From 2a36fd3fce6969a9ba277f13160528604292af0d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Jan 2025 16:53:41 +0530 Subject: [PATCH] Implement function to get metadata by name --- .../mtp/windows/content_enumeration.cpp | 10 ++++++ src/calibre/devices/mtp/windows/device.cpp | 35 +++++++++++++++++++ src/calibre/devices/mtp/windows/driver.py | 9 +++++ src/calibre/devices/mtp/windows/global.h | 1 + 4 files changed, 55 insertions(+) diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index 286507cea4..95fc245611 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -493,6 +493,16 @@ list_folder(IPortableDevice *device, CComPtr &content, I return ans.detach(); } +PyObject* +get_metadata(CComPtr &content, const wchar_t *object_id) { + CComPtr properties(create_filesystem_properties_collection()); + if (!properties) return NULL; + CComPtr devprops; + HRESULT hr = content->Properties(&devprops); + if (FAILED(hr)) { hresult_set_exc("Failed to get IPortableDeviceProperties interface", hr); return NULL; } + return get_object_properties(devprops, properties, object_id); +} + PyObject* find_in_parent(CComPtr &content, const wchar_t *parent_id, PyObject *name) { HRESULT hr; diff --git a/src/calibre/devices/mtp/windows/device.cpp b/src/calibre/devices/mtp/windows/device.cpp index d36e9413a5..2ce3a3e5f3 100644 --- a/src/calibre/devices/mtp/windows/device.cpp +++ b/src/calibre/devices/mtp/windows/device.cpp @@ -109,6 +109,37 @@ list_folder_by_name(Device *self, PyObject *args) { return wpd::list_folder(self->device, content, self->bulk_properties, parent_id.ptr()); } // }}} +// get_metadata_by_name() {{{ + +static PyObject* +get_metadata_by_name(Device *self, PyObject *args) { + wchar_raii parent_id; PyObject *names; + CComPtr 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, 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::get_metadata(content, parent_id.ptr()); +} // }}} + // create_folder() {{{ static PyObject* py_create_folder(Device *self, PyObject *args) { @@ -151,6 +182,10 @@ static PyMethodDef Device_methods[] = { "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_metadata_by_name", (PyCFunction)get_metadata_by_name, METH_VARARGS, + "get_metadata_by_name(parent_id, names) -> get metadata for the file or folder folder specified by names (a tuple of name components) relative to parent_id from the device. Return None or metadata." + }, + {"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." }, diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index a31992c042..36490c29bd 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -400,6 +400,15 @@ class MTP_DEVICE(MTPDeviceBase): raise DeviceError(f'Could not find folder named: {"/".join(names)} in {parent.full_path}') return list(x.values()) + @same_thread + def get_mtp_metadata_by_name(self, parent, *names: str): + if not parent.is_folder: + raise ValueError(f'{parent.full_path} is not a folder') + x = self.dev.get_metadata_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 def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: diff --git a/src/calibre/devices/mtp/windows/global.h b/src/calibre/devices/mtp/windows/global.h index 728ccc983e..2393f392bd 100644 --- a/src/calibre/devices/mtp/windows/global.h +++ b/src/calibre/devices/mtp/windows/global.h @@ -54,4 +54,5 @@ extern PyObject* find_in_parent( extern PyObject* list_folder( IPortableDevice *device, CComPtr &content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *folder_id); +extern PyObject* get_metadata(CComPtr &content, const wchar_t *object_id); }