From a476e821833833ed67feae777de09dbb0dce123a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Aug 2012 21:49:16 +0530 Subject: [PATCH] MTP driver: Implement getting files from the device --- src/calibre/devices/mtp/unix/driver.py | 21 ++++++- src/calibre/devices/mtp/unix/libmtp.c | 81 +++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index c680b57a17..cddd1c0f72 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -91,6 +91,12 @@ class MTP_DEVICE(MTPDeviceBase): self.filesystem_cache = None self.lock = RLock() self.blacklisted_devices = set() + for x in vars(self.detect.libmtp): + if x.startswith('LIBMTP'): + setattr(self, x, getattr(self.detect.libmtp, x)) + + def set_debug_level(self, lvl): + self.detect.libmtp.set_debug_level(lvl) def report_progress(self, sent, total): try: @@ -223,6 +229,10 @@ class MTP_DEVICE(MTPDeviceBase): if __name__ == '__main__': + class PR: + def report_progress(self, sent, total): + print (sent, total, end=', ') + from pprint import pprint dev = MTP_DEVICE(None) from calibre.devices.scanner import linux_scanner @@ -234,5 +244,14 @@ if __name__ == '__main__': print ("Storage info:") pprint(d.storage_info) print("Free space:", dev.free_space()) - dev.filesystem_cache.dump_filesystem() + # dev.filesystem_cache.dump_filesystem() + # with open('/tmp/flint.epub', 'wb') as f: + # print(d.get_file(786, f, PR())) + # print() + # with open('/tmp/bleak.epub', 'wb') as f: + # print(d.get_file(601, f, PR())) + # print() + dev.set_debug_level(dev.LIBMTP_DEBUG_ALL) + del d + dev.shutdown() diff --git a/src/calibre/devices/mtp/unix/libmtp.c b/src/calibre/devices/mtp/unix/libmtp.c index 895e49c47e..15842552ba 100644 --- a/src/calibre/devices/mtp/unix/libmtp.c +++ b/src/calibre/devices/mtp/unix/libmtp.c @@ -33,6 +33,7 @@ typedef struct { PyObject *obj; + PyObject *extra; PyThreadState *state; } ProgressCallback; @@ -64,6 +65,48 @@ static void dump_errorstack(LIBMTP_mtpdevice_t *dev, PyObject *list) { LIBMTP_Clear_Errorstack(dev); } +static uint16_t data_to_python(void *params, void *priv, uint32_t sendlen, unsigned char *data, uint32_t *putlen) { + PyObject *res; + ProgressCallback *cb; + uint16_t ret = LIBMTP_HANDLER_RETURN_OK; + + cb = (ProgressCallback *)priv; + *putlen = sendlen; + PyEval_RestoreThread(cb->state); + res = PyObject_CallMethod(cb->extra, "write", "s#", data, sendlen); + if (res == NULL) { + ret = LIBMTP_HANDLER_RETURN_ERROR; + *putlen = 0; + PyErr_Print(); + } else Py_DECREF(res); + + cb->state = PyEval_SaveThread(); + return ret; +} + +static uint16_t data_from_python(void *params, void *priv, uint32_t wantlen, unsigned char *data, uint32_t *gotlen) { + PyObject *res; + ProgressCallback *cb; + char *buf = NULL; + Py_ssize_t len = 0; + uint16_t ret = LIBMTP_HANDLER_RETURN_ERROR; + + *gotlen = 0; + + cb = (ProgressCallback *)priv; + PyEval_RestoreThread(cb->state); + res = PyObject_CallMethod(cb->extra, "read", "k", wantlen); + if (res != NULL && PyBytes_AsStringAndSize(res, &buf, &len) != -1 && len <= wantlen) { + memcpy(data, buf, len); + *gotlen = len; + ret = LIBMTP_HANDLER_RETURN_OK; + } else PyErr_Print(); + + Py_XDECREF(res); + cb->state = PyEval_SaveThread(); + return ret; +} + // }}} // Device object definition {{{ @@ -287,9 +330,11 @@ libmtp_Device_get_filelist(libmtp_Device *self, PyObject *args, PyObject *kwargs errs = PyList_New(0); if (ans == NULL || errs == NULL) { PyErr_NoMemory(); return NULL; } + Py_XINCREF(callback); cb.state = PyEval_SaveThread(); tf = LIBMTP_Get_Filelisting_With_Callback(self->device, report_progress, &cb); PyEval_RestoreThread(cb.state); + Py_XDECREF(callback); if (tf == NULL) { dump_errorstack(self->device, errs); @@ -380,6 +425,36 @@ libmtp_Device_get_folderlist(libmtp_Device *self, PyObject *args, PyObject *kwar } // }}} +// Device.get_file {{{ +static PyObject * +libmtp_Device_get_file(libmtp_Device *self, PyObject *args, PyObject *kwargs) { + PyObject *stream, *callback = NULL, *errs; + ProgressCallback cb; + uint32_t fileid; + int ret; + + ENSURE_DEV(NULL); ENSURE_STORAGE(NULL); + + + if (!PyArg_ParseTuple(args, "kO|O", &fileid, &stream, &callback)) return NULL; + errs = PyList_New(0); + if (errs == NULL) { PyErr_NoMemory(); return NULL; } + + cb.obj = callback; cb.extra = stream; + Py_XINCREF(callback); Py_INCREF(stream); + cb.state = PyEval_SaveThread(); + ret = LIBMTP_Get_File_To_Handler(self->device, fileid, data_to_python, &cb, report_progress, &cb); + PyEval_RestoreThread(cb.state); + Py_XDECREF(callback); Py_DECREF(stream); + + if (ret != 0) { + dump_errorstack(self->device, errs); + } + Py_XDECREF(PyObject_CallMethod(stream, "flush", NULL)); + return Py_BuildValue("ON", (ret == 0) ? Py_True : Py_False, errs); + +} // }}} + static PyMethodDef libmtp_Device_methods[] = { {"update_storage_info", (PyCFunction)libmtp_Device_update_storage_info, METH_VARARGS, "update_storage_info() -> Reread the storage info from the device (total, space, free space, storage locations, etc.)" @@ -390,7 +465,11 @@ static PyMethodDef libmtp_Device_methods[] = { }, {"get_folderlist", (PyCFunction)libmtp_Device_get_folderlist, METH_VARARGS, - "get_folderlist() -> Get the list of folders on the device. Returns files, erros." + "get_folderlist() -> Get the list of folders on the device. Returns files, errors." + }, + + {"get_file", (PyCFunction)libmtp_Device_get_file, METH_VARARGS, + "get_file(fileid, stream, callback=None) -> Get the file specified by fileid from the device. stream must be a file-like object. The file will be written to it. callback works the same as in get_filelist(). Returns ok, errs, where errs is a list of errors (if any)." }, {NULL} /* Sentinel */