From af3f6a62d9d1c27d3cba7a748aad0bab633d6364 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 6 Aug 2012 11:30:15 +0530 Subject: [PATCH] MTP driver: Get storage information --- src/calibre/devices/interface.py | 2 +- src/calibre/devices/mtp/unix/driver.py | 77 +++++++++++++++++- src/calibre/devices/mtp/unix/libmtp.c | 104 ++++++++++++++++++++++--- 3 files changed, 166 insertions(+), 17 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index fc4e5a2a60..4fd5fea252 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -297,7 +297,7 @@ class DevicePlugin(Plugin): :return: (device name, device version, software version on device, mime type) The tuple can optionally have a fifth element, which is a - drive information diction. See usbms.driver for an example. + drive information dictionary. See usbms.driver for an example. """ raise NotImplementedError() diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 081b09975f..91acae8a9d 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import time +import time, operator from threading import RLock from functools import wraps @@ -33,6 +33,11 @@ class MTP_DEVICE(MTPDeviceBase): self.lock = RLock() self.blacklisted_devices = set() + @synchronous + def get_gui_name(self): + if self.dev is None or not self.dev.friendly_name: return self.name + return self.dev.friendly_name + @synchronous def is_usb_connected(self, devices_on_system, debug=False, only_presence=False): @@ -62,6 +67,10 @@ class MTP_DEVICE(MTPDeviceBase): def post_yank_cleanup(self): self.dev = None + @synchronous + def shutdown(self): + self.dev = None + @synchronous def open(self, connected_device, library_uuid): def blacklist_device(): @@ -69,18 +78,78 @@ class MTP_DEVICE(MTPDeviceBase): self.blacklisted_devices.add((d.busnum, d.devnum, d.vendor_id, d.product_id, d.bcd, d.serial)) try: - self.detect.create_device(connected_device) + self.dev = self.detect.create_device(connected_device) except ValueError: # Give the device some time to settle time.sleep(2) try: - self.detect.create_device(connected_device) + self.dev = self.detect.create_device(connected_device) except ValueError: # Black list this device so that it is ignored for the # remainder of this session. blacklist_device() - raise OpenFailed('%s is not a MTP device'%connected_device) + raise OpenFailed('%s is not a MTP device'%(connected_device,)) except TypeError: blacklist_device() raise OpenFailed('') + storage = sorted(self.dev.storage_info, key=operator.itemgetter('id')) + if not storage: + blacklist_device() + raise OpenFailed('No storage found for device %s'%(connected_device,)) + self._main_id = storage[0]['id'] + self._carda_id = self._cardb_id = None + if len(storage) > 1: + self._carda_id = storage[1]['id'] + if len(storage) > 2: + self._cardb_id = storage[2]['id'] + + @synchronous + def get_device_information(self, end_session=True): + d = self.dev + return (d.friendly_name, d.device_version, d.device_version, '') + + @synchronous + def card_prefix(self, end_session=True): + ans = [None, None] + if self._carda_id is not None: + ans[0] = 'mtp:%d:'%self._carda_id + if self._cardb_id is not None: + ans[1] = 'mtp:%d:'%self._cardb_id + return tuple(ans) + + @synchronous + def total_space(self, end_session=True): + ans = [0, 0, 0] + for s in self.dev.storage_info: + i = {self._main_id:0, self._carda_id:1, + self._cardb_id:2}.get(s['id'], None) + if i is not None: + ans[i] = s['capacity'] + return tuple(ans) + + @synchronous + def free_space(self, end_session=True): + self.dev.update_storage_info() + ans = [0, 0, 0] + for s in self.dev.storage_info: + i = {self._main_id:0, self._carda_id:1, + self._cardb_id:2}.get(s['id'], None) + if i is not None: + ans[i] = s['freespace_bytes'] + return tuple(ans) + + +if __name__ == '__main__': + from pprint import pprint + dev = MTP_DEVICE(None) + from calibre.devices.scanner import linux_scanner + devs = linux_scanner() + mtp_devs = dev.detect(devs) + dev.open(list(mtp_devs)[0], 'xxx') + d = dev.dev + print ("Opened device:", dev.get_gui_name()) + print ("Storage info:") + pprint(d.storage_info) + print("Free space:", dev.free_space()) + diff --git a/src/calibre/devices/mtp/unix/libmtp.c b/src/calibre/devices/mtp/unix/libmtp.c index 0a818cfc66..d22f294157 100644 --- a/src/calibre/devices/mtp/unix/libmtp.c +++ b/src/calibre/devices/mtp/unix/libmtp.c @@ -6,6 +6,24 @@ #include "devices.h" +#define ENSURE_DEV(rval) \ + if (self->device == NULL) { \ + PyErr_SetString(PyExc_ValueError, "This device has not been initialized."); \ + return rval; \ + } + +// Storage types +#define ST_Undefined 0x0000 +#define ST_FixedROM 0x0001 +#define ST_RemovableROM 0x0002 +#define ST_FixedRAM 0x0003 +#define ST_RemovableRAM 0x0004 + +// Storage Access capability +#define AC_ReadWrite 0x0000 +#define AC_ReadOnly 0x0001 +#define AC_ReadOnly_with_Object_Deletion 0x0002 + // Device object definition {{{ typedef struct { PyObject_HEAD @@ -20,6 +38,7 @@ typedef struct { } libmtp_Device; +// Device.__init__() {{{ static void libmtp_Device_dealloc(libmtp_Device* self) { @@ -119,44 +138,100 @@ libmtp_Device_init(libmtp_Device *self, PyObject *args, PyObject *kwds) return 0; } +// }}} -// Collator.friendly_name {{{ +// Device.friendly_name {{{ static PyObject * libmtp_Device_friendly_name(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->friendly_name); + Py_INCREF(self->friendly_name); return self->friendly_name; } // }}} -// Collator.manufacturer_name {{{ +// Device.manufacturer_name {{{ static PyObject * libmtp_Device_manufacturer_name(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->manufacturer_name); + Py_INCREF(self->manufacturer_name); return self->manufacturer_name; } // }}} -// Collator.model_name {{{ +// Device.model_name {{{ static PyObject * libmtp_Device_model_name(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->model_name); + Py_INCREF(self->model_name); return self->model_name; } // }}} -// Collator.serial_number {{{ +// Device.serial_number {{{ static PyObject * libmtp_Device_serial_number(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->serial_number); + Py_INCREF(self->serial_number); return self->serial_number; } // }}} -// Collator.device_version {{{ +// Device.device_version {{{ static PyObject * libmtp_Device_device_version(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->device_version); + Py_INCREF(self->device_version); return self->device_version; } // }}} -// Collator.ids {{{ +// Device.ids {{{ static PyObject * libmtp_Device_ids(libmtp_Device *self, void *closure) { - return Py_BuildValue("O", self->ids); + Py_INCREF(self->ids); return self->ids; +} // }}} + +// Device.update_storage_info() {{{ +static PyObject* +libmtp_Device_update_storage_info(libmtp_Device *self, PyObject *args, PyObject *kwargs) { + ENSURE_DEV(NULL); + if (LIBMTP_Get_Storage(self->device, LIBMTP_STORAGE_SORTBY_NOTSORTED) < 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to get storage infor for device."); + return NULL; + } + Py_RETURN_NONE; +} +// }}} + +// Device.storage_info {{{ +static PyObject * +libmtp_Device_storage_info(libmtp_Device *self, void *closure) { + PyObject *ans, *loc; + LIBMTP_devicestorage_t *storage; + ENSURE_DEV(NULL); + if (self->device->storage == NULL) { PyErr_SetString(PyExc_RuntimeError, "The device has no storage information."); return NULL; } + + ans = PyList_New(0); + if (ans == NULL) { PyErr_NoMemory(); return NULL; } + + for (storage = self->device->storage; storage != NULL; storage = storage->next) { + // Ignore read only storage + if (storage->StorageType == ST_FixedROM || storage->StorageType == ST_RemovableROM) continue; + // Storage IDs with the lower 16 bits 0x0000 are not supposed to be + // writeable. + if ((storage->id & 0x0000FFFFU) == 0x00000000U) continue; + // Also check the access capability to avoid e.g. deletable only storages + if (storage->AccessCapability == AC_ReadOnly || storage->AccessCapability == AC_ReadOnly_with_Object_Deletion) continue; + + loc = Py_BuildValue("{s:I,s:O,s:K,s:K,s:K,s:s,s:s}", + "id", storage->id, + "removable", ((storage->StorageType == ST_RemovableRAM) ? Py_True : Py_False), + "capacity", storage->MaxCapacity, + "freespace_bytes", storage->FreeSpaceInBytes, + "freespace_objects", storage->FreeSpaceInObjects, + "storage_desc", storage->StorageDescription, + "volume_id", storage->VolumeIdentifier + ); + + if (loc == NULL) return NULL; + if (PyList_Append(ans, loc) != 0) return NULL; + Py_DECREF(loc); + + } + + return ans; } // }}} 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.)" + }, + {NULL} /* Sentinel */ }; @@ -191,6 +266,11 @@ static PyGetSetDef libmtp_Device_getsetters[] = { (char *)"The ids of the device (busnum, devnum, vendor_id, product_id, usb_serialnum)", NULL}, + {(char *)"storage_info", + (getter)libmtp_Device_storage_info, NULL, + (char *)"Information about the storage locations on the device. Returns a list of dictionaries where each dictionary corresponds to the LIBMTP_devicestorage_struct.", + NULL}, + {NULL} /* Sentinel */ };