mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on names based interface for windows mtp driver
This commit is contained in:
parent
6865c5eea3
commit
f19ca06b86
@ -32,7 +32,7 @@ pump_waiting_messages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static IPortableDeviceKeyCollection*
|
static IPortableDeviceKeyCollection*
|
||||||
create_filesystem_properties_collection() { // {{{
|
create_filesystem_properties_collection(bool for_name_query = false) { // {{{
|
||||||
CComPtr<IPortableDeviceKeyCollection> properties;
|
CComPtr<IPortableDeviceKeyCollection> properties;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
@ -45,17 +45,19 @@ create_filesystem_properties_collection() { // {{{
|
|||||||
#define ADDPROP(x) hr = properties->Add(x); if (FAILED(hr)) { hresult_set_exc("Failed to add property " #x " to filesystem properties collection", hr); return NULL; }
|
#define ADDPROP(x) hr = properties->Add(x); if (FAILED(hr)) { hresult_set_exc("Failed to add property " #x " to filesystem properties collection", hr); return NULL; }
|
||||||
|
|
||||||
ADDPROP(WPD_OBJECT_CONTENT_TYPE);
|
ADDPROP(WPD_OBJECT_CONTENT_TYPE);
|
||||||
ADDPROP(WPD_OBJECT_PARENT_ID);
|
|
||||||
ADDPROP(WPD_OBJECT_PERSISTENT_UNIQUE_ID);
|
|
||||||
ADDPROP(WPD_OBJECT_NAME);
|
|
||||||
ADDPROP(WPD_OBJECT_ORIGINAL_FILE_NAME);
|
ADDPROP(WPD_OBJECT_ORIGINAL_FILE_NAME);
|
||||||
// ADDPROP(WPD_OBJECT_SYNC_ID);
|
if (!for_name_query) {
|
||||||
ADDPROP(WPD_OBJECT_ISSYSTEM);
|
ADDPROP(WPD_OBJECT_PARENT_ID);
|
||||||
ADDPROP(WPD_OBJECT_ISHIDDEN);
|
ADDPROP(WPD_OBJECT_PERSISTENT_UNIQUE_ID);
|
||||||
ADDPROP(WPD_OBJECT_CAN_DELETE);
|
ADDPROP(WPD_OBJECT_NAME);
|
||||||
ADDPROP(WPD_OBJECT_SIZE);
|
// ADDPROP(WPD_OBJECT_SYNC_ID);
|
||||||
ADDPROP(WPD_OBJECT_DATE_CREATED);
|
ADDPROP(WPD_OBJECT_ISSYSTEM);
|
||||||
ADDPROP(WPD_OBJECT_DATE_MODIFIED);
|
ADDPROP(WPD_OBJECT_ISHIDDEN);
|
||||||
|
ADDPROP(WPD_OBJECT_CAN_DELETE);
|
||||||
|
ADDPROP(WPD_OBJECT_SIZE);
|
||||||
|
ADDPROP(WPD_OBJECT_DATE_CREATED);
|
||||||
|
ADDPROP(WPD_OBJECT_DATE_MODIFIED);
|
||||||
|
}
|
||||||
#undef ADDPROP
|
#undef ADDPROP
|
||||||
|
|
||||||
return properties.Detach();
|
return properties.Detach();
|
||||||
@ -363,6 +365,23 @@ find_objects_in(CComPtr<IPortableDeviceContent> &content, CComPtr<IPortableDevic
|
|||||||
|
|
||||||
// Single get filesystem {{{
|
// Single get filesystem {{{
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
get_object_filename(IPortableDeviceProperties *devprops, IPortableDeviceKeyCollection *properties, const wchar_t *object_id) {
|
||||||
|
CComPtr<IPortableDeviceValues> values;
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
hr = devprops->GetValues(object_id, properties, &values);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to get properties for object", hr); return NULL; }
|
||||||
|
com_wchar_raii property;
|
||||||
|
hr = values->GetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, property.unsafe_address());
|
||||||
|
if (SUCCEEDED(hr)) return PyUnicode_FromWideChar(property.ptr(), -1);
|
||||||
|
hresult_set_exc("Failed to get original file name for object", hr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
get_object_properties(IPortableDeviceProperties *devprops, IPortableDeviceKeyCollection *properties, const wchar_t *object_id) {
|
get_object_properties(IPortableDeviceProperties *devprops, IPortableDeviceKeyCollection *properties, const wchar_t *object_id) {
|
||||||
CComPtr<IPortableDeviceValues> values;
|
CComPtr<IPortableDeviceValues> values;
|
||||||
@ -448,6 +467,79 @@ create_object_properties(const wchar_t *parent_id, const wchar_t *name, const GU
|
|||||||
return values.Detach();
|
return values.Detach();
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
list_folder(CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *folder_id) {
|
||||||
|
HRESULT hr;
|
||||||
|
CComPtr<IPortableDevicePropVariantCollection> object_ids;
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
hr = object_ids.CoCreateInstance(CLSID_PortableDevicePropVariantCollection, NULL, CLSCTX_INPROC_SERVER);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); return NULL; }
|
||||||
|
|
||||||
|
bool enum_failed = false;
|
||||||
|
if (!find_objects_in(content, object_ids, folder_id, &enum_failed)) return NULL;
|
||||||
|
DWORD num;
|
||||||
|
CComPtr<IPortableDeviceProperties> devprops;
|
||||||
|
|
||||||
|
hr = content->Properties(&devprops);
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to get IPortableDeviceProperties interface", hr); return NULL; }
|
||||||
|
|
||||||
|
CComPtr<IPortableDeviceKeyCollection> properties(create_filesystem_properties_collection());
|
||||||
|
if (!properties) return NULL;
|
||||||
|
hr = object_ids->GetCount(&num);
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to get object id count", hr); return NULL; }
|
||||||
|
pyobject_raii ans(PyList_New(0)); if (!ans) return NULL;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < num; i++) {
|
||||||
|
prop_variant pv;
|
||||||
|
hr = object_ids->GetAt(i, &pv);
|
||||||
|
if (SUCCEEDED(hr) && pv.pwszVal != NULL) {
|
||||||
|
pyobject_raii item(get_object_properties(devprops, properties, pv.pwszVal));
|
||||||
|
if (!item) return NULL;
|
||||||
|
if (PyList_Append(ans.ptr(), item.ptr()) != 0) return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
find_in_parent(CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *parent_id, PyObject *name) {
|
||||||
|
HRESULT hr;
|
||||||
|
CComPtr<IPortableDevicePropVariantCollection> object_ids;
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
hr = object_ids.CoCreateInstance(CLSID_PortableDevicePropVariantCollection, NULL, CLSCTX_INPROC_SERVER);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); return NULL; }
|
||||||
|
|
||||||
|
bool enum_failed = false;
|
||||||
|
if (!find_objects_in(content, object_ids, parent_id, &enum_failed)) return NULL;
|
||||||
|
DWORD num;
|
||||||
|
CComPtr<IPortableDeviceProperties> devprops;
|
||||||
|
|
||||||
|
hr = content->Properties(&devprops);
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to get IPortableDeviceProperties interface", hr); return NULL; }
|
||||||
|
|
||||||
|
CComPtr<IPortableDeviceKeyCollection> properties(create_filesystem_properties_collection(true));
|
||||||
|
if (!properties) return NULL;
|
||||||
|
|
||||||
|
hr = object_ids->GetCount(&num);
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to get object id count", hr); return NULL; }
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < num; i++) {
|
||||||
|
prop_variant pv;
|
||||||
|
hr = object_ids->GetAt(i, &pv);
|
||||||
|
if (SUCCEEDED(hr) && pv.pwszVal != NULL) {
|
||||||
|
pyobject_raii item(get_object_filename(devprops, properties, pv.pwszVal));
|
||||||
|
if (!item) { if (PyErr_Occurred()) { PyErr_Clear(); }; continue; }
|
||||||
|
pyobject_raii q(PyObject_CallMethod(item.ptr(), "lower", NULL));
|
||||||
|
if (!q) return NULL;
|
||||||
|
if (PyUnicode_Compare(q.ptr(), name) == 0) return PyUnicode_FromWideChar(pv.pwszVal, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
get_files_and_folders(unsigned int level, IPortableDevice *device, CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *parent_id, PyObject *callback, PyObject *ans) { // {{{
|
get_files_and_folders(unsigned int level, IPortableDevice *device, CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *parent_id, PyObject *callback, PyObject *ans) { // {{{
|
||||||
CComPtr<IPortableDevicePropVariantCollection> object_ids;
|
CComPtr<IPortableDevicePropVariantCollection> object_ids;
|
||||||
|
@ -78,6 +78,37 @@ py_get_file(Device *self, PyObject *args) {
|
|||||||
return wpd::get_file(self->device, object.ptr(), stream, callback);
|
return wpd::get_file(self->device, object.ptr(), stream, callback);
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
// list_folder_by_name() {{{
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
list_folder_by_name(Device *self, PyObject *args) {
|
||||||
|
wchar_raii parent_id; PyObject *names;
|
||||||
|
CComPtr<IPortableDeviceContent> content;
|
||||||
|
HRESULT hr; bool found = false;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
hr = self->device->Content(&content);
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
if (FAILED(hr)) { hresult_set_exc("Failed to create content interface", hr); return NULL; }
|
||||||
|
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O&O!", py_to_wchar, &parent_id, &PyTuple_Type, &names)) return NULL;
|
||||||
|
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(names); i++) {
|
||||||
|
PyObject *k = PyTuple_GET_ITEM(names, i);
|
||||||
|
if (!PyUnicode_Check(k)) { PyErr_SetString(PyExc_TypeError, "names must contain only unicode strings"); return NULL; }
|
||||||
|
pyobject_raii l(PyObject_CallMethod(k, "lower", NULL)); if (!l) return NULL;
|
||||||
|
pyobject_raii object_id(wpd::find_in_parent(content, self->bulk_properties, parent_id.ptr(), l.ptr()));
|
||||||
|
if (!object_id) {
|
||||||
|
if (PyErr_Occurred()) return NULL;
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
if (!py_to_wchar_(object_id.ptr(), &parent_id)) return NULL;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found) Py_RETURN_NONE;
|
||||||
|
return wpd::list_folder(content, self->bulk_properties, parent_id.ptr());
|
||||||
|
} // }}}
|
||||||
|
|
||||||
// create_folder() {{{
|
// create_folder() {{{
|
||||||
static PyObject*
|
static PyObject*
|
||||||
py_create_folder(Device *self, PyObject *args) {
|
py_create_folder(Device *self, PyObject *args) {
|
||||||
@ -116,6 +147,10 @@ static PyMethodDef Device_methods[] = {
|
|||||||
"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 is called as (object, level). It is called with every found object. If the callback returns False and the object is a folder, it is not recursed into."
|
"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 is called as (object, level). It is called with every found object. If the callback returns False and the object is a folder, it is not recursed into."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{"list_folder_by_name", (PyCFunction)list_folder_by_name, METH_VARARGS,
|
||||||
|
"list_folder_by_name(parent_id, names) -> List the folder specified by names (a tuple of name components) relative to parent_id from the device. Return None or a list of entries."
|
||||||
|
},
|
||||||
|
|
||||||
{"get_file", (PyCFunction)py_get_file, METH_VARARGS,
|
{"get_file", (PyCFunction)py_get_file, METH_VARARGS,
|
||||||
"get_file(object_id, stream, callback=None) -> Get the file identified by object_id from the device. The file is written to the stream object, which must be a file like object. If callback is not None, it must be a callable that accepts two arguments: (bytes_read, total_size). It will be called after each chunk is read from the device. Note that it can be called multiple times with the same values."
|
"get_file(object_id, stream, callback=None) -> Get the file identified by object_id from the device. The file is written to the stream object, which must be a file like object. If callback is not None, it must be a callable that accepts two arguments: (bytes_read, total_size). It will be called after each chunk is read from the device. Note that it can be called multiple times with the same values."
|
||||||
},
|
},
|
||||||
|
@ -391,6 +391,15 @@ class MTP_DEVICE(MTPDeviceBase):
|
|||||||
ans[i] = s['free_space']
|
ans[i] = s['free_space']
|
||||||
return tuple(ans)
|
return tuple(ans)
|
||||||
|
|
||||||
|
@same_thread
|
||||||
|
def list_mtp_folder_by_name(self, parent, *names: str):
|
||||||
|
if not parent.is_folder:
|
||||||
|
raise ValueError(f'{parent.full_path} is not a folder')
|
||||||
|
x = self.dev.list_folder_by_name(parent.object_id, names)
|
||||||
|
if x is None:
|
||||||
|
raise DeviceError(f'Could not find folder named: {"/".join(names)} in {parent.full_path}')
|
||||||
|
return x
|
||||||
|
|
||||||
@same_thread
|
@same_thread
|
||||||
def get_mtp_file(self, f, stream=None, callback=None):
|
def get_mtp_file(self, f, stream=None, callback=None):
|
||||||
if f.is_folder:
|
if f.is_folder:
|
||||||
|
@ -49,5 +49,8 @@ extern PyObject* get_file(IPortableDevice *device, const wchar_t *object_id, PyO
|
|||||||
extern PyObject* create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name);
|
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);
|
extern PyObject* delete_object(IPortableDevice *device, const wchar_t *object_id);
|
||||||
extern PyObject* put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback);
|
extern PyObject* put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback);
|
||||||
|
extern PyObject* find_in_parent(
|
||||||
|
CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *parent_id, PyObject *name);
|
||||||
|
extern PyObject* list_folder(
|
||||||
|
CComPtr<IPortableDeviceContent> &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *folder_id);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user