MTP drivers: Provide feedback during filesystem scan

This commit is contained in:
Kovid Goyal 2012-09-29 12:15:46 +05:30
parent 3733d739f7
commit dbf4a84950
7 changed files with 51 additions and 23 deletions

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -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();

View File

@ -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,

View File

@ -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())

View File

@ -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);