diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index 2fe0843484..be2bab7638 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -155,6 +155,9 @@ class MTP_DEVICE(BASE): # }}} # Get list of books from device, with metadata {{{ + def filesystem_callback(self, msg): + self.report_progress(0, msg) + def books(self, oncard=None, end_session=True): from calibre.devices.mtp.books import JSONCodec from calibre.devices.mtp.books import BookList, Book diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 71914cddc0..b8e8938c93 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -212,6 +212,9 @@ class MTP_DEVICE(MTPDeviceBase): ans += pprint.pformat(storage) return ans + def _filesystem_callback(self, entry): + self.filesystem_callback(_('Found object: %s')%entry.get('name', '')) + @property def filesystem_cache(self): if self._filesystem_cache is None: @@ -231,7 +234,8 @@ class MTP_DEVICE(MTPDeviceBase): storage.append({'id':sid, 'size':capacity, 'is_folder':True, 'name':name, 'can_delete':False, 'is_system':True}) - items, errs = self.dev.get_filesystem(sid) + items, errs = self.dev.get_filesystem(sid, + self._filesystem_callback) all_items.extend(items), all_errs.extend(errs) if not all_items and all_errs: raise DeviceError( diff --git a/src/calibre/devices/mtp/unix/libmtp.c b/src/calibre/devices/mtp/unix/libmtp.c index bf07c73a35..b62bd8a9c7 100644 --- a/src/calibre/devices/mtp/unix/libmtp.c +++ b/src/calibre/devices/mtp/unix/libmtp.c @@ -357,7 +357,7 @@ Device_storage_info(Device *self, void *closure) { // Device.get_filesystem {{{ -static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uint32_t parent_id, PyObject *ans, PyObject *errs) { +static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uint32_t parent_id, PyObject *ans, PyObject *errs, PyObject *callback) { LIBMTP_file_t *f, *files; PyObject *entry; int ok = 1; @@ -372,12 +372,13 @@ static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uin entry = build_file_metadata(f, storage_id); if (entry == NULL) { ok = 0; } else { + Py_XDECREF(PyObject_CallFunctionObjArgs(callback, entry, NULL)); if (PyList_Append(ans, entry) != 0) { ok = 0; } Py_DECREF(entry); } if (ok && f->filetype == LIBMTP_FILETYPE_FOLDER) { - if (!recursive_get_files(dev, storage_id, f->item_id, ans, errs)) { + if (!recursive_get_files(dev, storage_id, f->item_id, ans, errs, callback)) { ok = 0; } } @@ -394,19 +395,20 @@ static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uin static PyObject * Device_get_filesystem(Device *self, PyObject *args) { - PyObject *ans, *errs; + PyObject *ans, *errs, *callback; unsigned long storage_id; int ok = 0; ENSURE_DEV(NULL); ENSURE_STORAGE(NULL); - if (!PyArg_ParseTuple(args, "k", &storage_id)) return NULL; + if (!PyArg_ParseTuple(args, "kO", &storage_id, &callback)) return NULL; + if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback is not a callable"); return NULL; } ans = PyList_New(0); errs = PyList_New(0); if (errs == NULL || ans == NULL) { PyErr_NoMemory(); return NULL; } LIBMTP_Clear_Errorstack(self->device); - ok = recursive_get_files(self->device, (uint32_t)storage_id, 0, ans, errs); + ok = recursive_get_files(self->device, (uint32_t)storage_id, 0, ans, errs, callback); dump_errorstack(self->device, errs); if (!ok) { Py_DECREF(ans); @@ -535,7 +537,7 @@ static PyMethodDef Device_methods[] = { }, {"get_filesystem", (PyCFunction)Device_get_filesystem, METH_VARARGS, - "get_filesystem(storage_id) -> Get the list of files and folders on the device in storage_id. Returns files, errors." + "get_filesystem(storage_id, callback) -> Get the list of files and folders on the device in storage_id. Returns files, errors. callback must be a callable that accepts a single argument. It is called with every found object." }, {"get_file", (PyCFunction)Device_get_file, METH_VARARGS, diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index 580f77f9b0..612ecbc915 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -136,8 +136,9 @@ public: HANDLE complete; ULONG self_ref; PyThreadState *thread_state; + PyObject *callback; - GetBulkCallback(PyObject *items_dict, HANDLE ev) : items(items_dict), complete(ev), self_ref(1), thread_state(NULL) {} + GetBulkCallback(PyObject *items_dict, HANDLE ev, PyObject* pycallback) : items(items_dict), complete(ev), self_ref(1), thread_state(NULL), callback(pycallback) {} ~GetBulkCallback() {} HRESULT __stdcall OnStart(REFGUID Context) { return S_OK; } @@ -195,6 +196,7 @@ public: Py_DECREF(temp); set_properties(obj, properties); + Py_XDECREF(PyObject_CallFunctionObjArgs(callback, obj, NULL)); properties->Release(); properties = NULL; } @@ -207,7 +209,7 @@ public: }; -static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids) { +static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids, PyObject *pycallback) { PyObject *folders = NULL; GUID guid_context = GUID_NULL; HANDLE ev = NULL; @@ -227,7 +229,7 @@ static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePro properties = create_filesystem_properties_collection(); if (properties == NULL) goto end; - callback = new (std::nothrow) GetBulkCallback(folders, ev); + callback = new (std::nothrow) GetBulkCallback(folders, ev, pycallback); if (callback == NULL) { PyErr_NoMemory(); goto end; } hr = bulk_properties->QueueGetValuesByObjectList(object_ids, properties, callback, &guid_context); @@ -272,7 +274,7 @@ end: // }}} // find_all_objects_in() {{{ -static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevicePropVariantCollection *object_ids, const wchar_t *parent_id) { +static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevicePropVariantCollection *object_ids, const wchar_t *parent_id, PyObject *callback) { /* * Find all children of the object identified by parent_id, recursively. * The child ids are put into object_ids. Returns False if any errors @@ -284,6 +286,7 @@ static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevice DWORD fetched, i; PROPVARIANT pv; BOOL ok = 1; + PyObject *id; PropVariantInit(&pv); pv.vt = VT_LPWSTR; @@ -303,10 +306,15 @@ static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevice if (SUCCEEDED(hr)) { for(i = 0; i < fetched; i++) { pv.pwszVal = child_ids[i]; + id = wchar_to_unicode(pv.pwszVal); + if (id != NULL) { + Py_XDECREF(PyObject_CallFunctionObjArgs(callback, id, NULL)); + Py_DECREF(id); + } hr2 = object_ids->Add(&pv); pv.pwszVal = NULL; if (FAILED(hr2)) { hresult_set_exc("Failed to add child ids to propvariantcollection", hr2); break; } - ok = find_all_objects_in(content, object_ids, child_ids[i]); + ok = find_all_objects_in(content, object_ids, child_ids[i], callback); if (!ok) break; } for (i = 0; i < fetched; i++) { CoTaskMemFree(child_ids[i]); child_ids[i] = NULL; } @@ -347,7 +355,7 @@ end: return ans; } -static PyObject* single_get_filesystem(IPortableDeviceContent *content, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids) { +static PyObject* single_get_filesystem(IPortableDeviceContent *content, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids, PyObject *callback) { DWORD num, i; PROPVARIANT pv; HRESULT hr; @@ -375,6 +383,7 @@ static PyObject* single_get_filesystem(IPortableDeviceContent *content, const wc if (SUCCEEDED(hr) && pv.pwszVal != NULL) { item = get_object_properties(devprops, properties, pv.pwszVal); if (item != NULL) { + Py_XDECREF(PyObject_CallFunctionObjArgs(callback, item, NULL)); PyDict_SetItem(ans, PyDict_GetItemString(item, "id"), item); Py_DECREF(item); item = NULL; ok = 1; @@ -429,7 +438,7 @@ end: return values; } // }}} -PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties) { // {{{ +PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties, PyObject *callback) { // {{{ PyObject *folders = NULL; IPortableDevicePropVariantCollection *object_ids = NULL; IPortableDeviceContent *content = NULL; @@ -447,11 +456,11 @@ PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id Py_END_ALLOW_THREADS; if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); goto end; } - ok = find_all_objects_in(content, object_ids, storage_id); + ok = find_all_objects_in(content, object_ids, storage_id, callback); if (!ok) goto end; - if (bulk_properties != NULL) folders = bulk_get_filesystem(device, bulk_properties, storage_id, object_ids); - else folders = single_get_filesystem(content, storage_id, object_ids); + if (bulk_properties != NULL) folders = bulk_get_filesystem(device, bulk_properties, storage_id, object_ids, callback); + else folders = single_get_filesystem(content, storage_id, object_ids, callback); end: if (content != NULL) content->Release(); diff --git a/src/calibre/devices/mtp/windows/device.cpp b/src/calibre/devices/mtp/windows/device.cpp index 3d8d442b6c..3886bb5e56 100644 --- a/src/calibre/devices/mtp/windows/device.cpp +++ b/src/calibre/devices/mtp/windows/device.cpp @@ -78,14 +78,15 @@ update_data(Device *self, PyObject *args) { // get_filesystem() {{{ static PyObject* py_get_filesystem(Device *self, PyObject *args) { - PyObject *storage_id, *ret; + PyObject *storage_id, *ret, *callback; wchar_t *storage; - if (!PyArg_ParseTuple(args, "O", &storage_id)) return NULL; + if (!PyArg_ParseTuple(args, "OO", &storage_id, &callback)) return NULL; + if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback is not a callable"); return NULL; } storage = unicode_to_wchar(storage_id); if (storage == NULL) return NULL; - ret = wpd::get_filesystem(self->device, storage, self->bulk_properties); + ret = wpd::get_filesystem(self->device, storage, self->bulk_properties, callback); free(storage); return ret; } // }}} @@ -163,7 +164,7 @@ static PyMethodDef Device_methods[] = { }, {"get_filesystem", (PyCFunction)py_get_filesystem, METH_VARARGS, - "get_filesystem(storage_id) -> Get all files/folders on the storage identified by storage_id. Tries to use bulk operations when possible." + "get_filesystem(storage_id, callback) -> Get all files/folders on the storage identified by storage_id. Tries to use bulk operations when possible. callback must be a callable that accepts a single argument. It is called with every found id and then with the metadata for every id." }, {"get_file", (PyCFunction)py_get_file, METH_VARARGS, diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index 202c8dfd6e..7253b4490c 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -214,6 +214,14 @@ class MTP_DEVICE(MTPDeviceBase): return True + def _filesystem_callback(self, obj): + if isinstance(obj, dict): + n = obj.get('name', '') + msg = _('Found object: %s')%n + else: + msg = _('Found id: %s')%obj + self.filesystem_callback(msg) + @property def filesystem_cache(self): if self._filesystem_cache is None: @@ -233,7 +241,8 @@ class MTP_DEVICE(MTPDeviceBase): break storage = {'id':storage_id, 'size':capacity, 'name':name, 'is_folder':True, 'can_delete':False, 'is_system':True} - id_map = self.dev.get_filesystem(storage_id) + id_map = self.dev.get_filesystem(storage_id, + self._filesystem_callback) for x in id_map.itervalues(): x['storage_id'] = storage_id all_storage.append(storage) items.append(id_map.itervalues()) diff --git a/src/calibre/devices/mtp/windows/global.h b/src/calibre/devices/mtp/windows/global.h index 212afd2cec..2a9361c18b 100644 --- a/src/calibre/devices/mtp/windows/global.h +++ b/src/calibre/devices/mtp/windows/global.h @@ -56,7 +56,7 @@ int pump_waiting_messages(); extern IPortableDeviceValues* get_client_information(); extern IPortableDevice* open_device(const wchar_t *pnp_id, IPortableDeviceValues *client_information); extern PyObject* get_device_information(IPortableDevice *device, IPortableDevicePropertiesBulk **bulk_properties); -extern PyObject* get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties); +extern PyObject* get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties, PyObject *callback); extern PyObject* get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, PyObject *callback); extern PyObject* create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name); extern PyObject* delete_object(IPortableDevice *device, const wchar_t *object_id);