WPD: Implement opening device and getting basic device info.

This commit is contained in:
Kovid Goyal 2012-08-14 17:08:56 +05:30
parent ba22a7a5ca
commit 585b9a5fea
8 changed files with 257 additions and 17 deletions

View file

@ -172,6 +172,7 @@ def __init__(self, name, sources, **kwargs):
[
'calibre/devices/mtp/windows/utils.cpp',
'calibre/devices/mtp/windows/device_enumeration.cpp',
'calibre/devices/mtp/windows/device.cpp',
'calibre/devices/mtp/windows/wpd.cpp',
],
headers=[

View file

@ -39,6 +39,7 @@ class MTPDeviceBase(DevicePlugin):
def __init__(self, *args, **kwargs):
DevicePlugin.__init__(self, *args, **kwargs)
self.progress_reporter = None
self.current_friendly_name = None
def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None):
@ -47,3 +48,7 @@ def reset(self, key='-1', log_packets=False, report_progress=None,
def set_progress_reporter(self, report_progress):
self.progress_reporter = report_progress
def get_gui_name(self):
return self.current_friendly_name or self.name

View file

@ -14,7 +14,7 @@
from io import BytesIO
from calibre import prints
from calibre.devices.errors import OpenFailed
from calibre.devices.errors import OpenFailed, DeviceError
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
from calibre.devices.mtp.unix.detect import MTPDetect
@ -102,11 +102,6 @@ def report_progress(self, sent, total):
if self.progress_reporter is not None:
self.progress_reporter(p)
@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):
@ -134,7 +129,7 @@ def is_usb_connected(self, devices_on_system, debug=False,
@synchronous
def post_yank_cleanup(self):
self.dev = self.filesystem_cache = None
self.dev = self.filesystem_cache = self.current_friendly_name = None
@synchronous
def startup(self):
@ -184,15 +179,18 @@ def blacklist_device():
self._carda_id = storage[1]['id']
if len(storage) > 2:
self._cardb_id = storage[2]['id']
self.current_friendly_name = self.dev.name
@synchronous
def read_filesystem_cache(self):
try:
files, errs = self.dev.get_filelist(self)
if errs and not files:
raise OpenFailed('Failed to read files from device. Underlying errors:\n'
raise DeviceError('Failed to read files from device. Underlying errors:\n'
+self.format_errorstack(errs))
folders, errs = self.dev.get_folderlist()
if errs and not folders:
raise OpenFailed('Failed to read folders from device. Underlying errors:\n'
raise DeviceError('Failed to read folders from device. Underlying errors:\n'
+self.format_errorstack(errs))
self.filesystem_cache = FilesystemCache(files, folders)
except:
@ -202,15 +200,15 @@ def blacklist_device():
@synchronous
def get_device_information(self, end_session=True):
d = self.dev
return (d.friendly_name, d.device_version, d.device_version, '')
return (self.current_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
ans[0] = 'mtp:::%d:::'%self._carda_id
if self._cardb_id is not None:
ans[1] = 'mtp:%d:'%self._cardb_id
ans[1] = 'mtp:::%d:::'%self._cardb_id
return tuple(ans)
@synchronous
@ -248,6 +246,7 @@ def report_progress(self, sent, total):
devs = linux_scanner()
mtp_devs = dev.detect(devs)
dev.open(list(mtp_devs)[0], 'xxx')
dev.read_filesystem_cache()
d = dev.dev
print ("Opened device:", dev.get_gui_name())
print ("Storage info:")

View file

@ -0,0 +1,137 @@
/*
* device.cpp
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "global.h"
extern IPortableDevice* wpd::open_device(const wchar_t *pnp_id, IPortableDeviceValues *client_information);
extern IPortableDeviceValues* wpd::get_client_information();
extern PyObject* wpd::get_device_information(IPortableDevice *device);
using namespace wpd;
// Device.__init__() {{{
static void
dealloc(Device* self)
{
if (self->pnp_id != NULL) free(self->pnp_id);
self->pnp_id = NULL;
if (self->device != NULL) {
Py_BEGIN_ALLOW_THREADS;
self->device->Close(); self->device->Release();
self->device = NULL;
Py_END_ALLOW_THREADS;
}
if (self->client_information != NULL) { self->client_information->Release(); self->client_information = NULL; }
Py_XDECREF(self->device_information); self->device_information = NULL;
self->ob_type->tp_free((PyObject*)self);
}
static int
init(Device *self, PyObject *args, PyObject *kwds)
{
PyObject *pnp_id;
int ret = -1;
if (!PyArg_ParseTuple(args, "O", &pnp_id)) return -1;
self->pnp_id = unicode_to_wchar(pnp_id);
if (self->pnp_id == NULL) return -1;
self->client_information = get_client_information();
if (self->client_information != NULL) {
self->device = open_device(self->pnp_id, self->client_information);
if (self->device != NULL) {
self->device_information = get_device_information(self->device);
if (self->device_information != NULL) ret = 0;
}
}
return ret;
}
// }}}
// update_device_data() {{{
static PyObject*
update_data(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *di = NULL;
di = get_device_information(self->device);
if (di == NULL) return NULL;
Py_XDECREF(self->device_information); self->device_information = di;
Py_RETURN_NONE;
} // }}}
static PyMethodDef Device_methods[] = {
{"update_data", (PyCFunction)update_data, METH_VARARGS,
"update_data() -> Reread the basic device data from the device (total, space, free space, storage locations, etc.)"
},
{NULL}
};
// Device.data {{{
static PyObject *
Device_data(Device *self, void *closure) {
Py_INCREF(self->device_information); return self->device_information;
} // }}}
static PyGetSetDef Device_getsetters[] = {
{(char *)"data",
(getter)Device_data, NULL,
(char *)"The basic device information.",
NULL},
{NULL} /* Sentinel */
};
PyTypeObject wpd::DeviceType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"wpd.Device", /*tp_name*/
sizeof(Device), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
"Device", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Device_methods, /* tp_methods */
0, /* tp_members */
Device_getsetters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
}; // }}}

View file

@ -12,6 +12,7 @@
from calibre import as_unicode, prints
from calibre.constants import plugins, __appname__, numeric_version
from calibre.devices.errors import OpenFailed
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
class MTP_DEVICE(MTPDeviceBase):
@ -29,6 +30,7 @@ def __init__(self, *args, **kwargs):
self.previous_devices_on_system = frozenset()
self.last_refresh_devices_time = time.time()
self.wpd = self.wpd_error = None
self._main_id = self._carda_id = self._cardb_id = None
@synchronous
def startup(self):
@ -78,9 +80,13 @@ def detect_managed_devices(self, devices_on_system):
as_unicode(e))
data = {} if data is False else False
self.detected_devices[dev] = data
# Remove devices that have been disconnected from ejected
# devices
self.ejected_devices = set(self.detected_devices).intersection(self.ejected_devices)
# Remove devices that have been disconnected from ejected
# devices and blacklisted devices
self.ejected_devices = set(self.detected_devices).intersection(
self.ejected_devices)
self.blacklisted_devices = set(self.detected_devices).intersection(
self.blacklisted_devices)
if self.currently_connected_pnp_id is not None:
return (self.currently_connected_pnp_id if
@ -114,12 +120,80 @@ def is_suitable_wpd_device(self, devdata):
@synchronous
def post_yank_cleanup(self):
self.currently_connected_pnp_id = None
self.currently_connected_pnp_id = self.current_friendly_name = None
self._main_id = self._carda_id = self._cardb_id = None
self.dev = None
@synchronous
def eject(self):
if self.currently_connected_pnp_id is None: return
self.ejected_devices.add(self.currently_connected_pnp_id)
self.currently_connected_pnp_id = None
self.currently_connected_pnp_id = self.current_friendly_name = None
self._main_id = self._carda_id = self._cardb_id = None
self.dev = None
@synchronous
def open(self, connected_device, library_uuid):
self.dev = self.filesystem_cache = None
try:
self.dev = self.wpd.Device(connected_device)
except self.wpd.WPDError:
time.sleep(2)
try:
self.dev = self.wpd.Device(connected_device)
except self.wpd.WPDError as e:
self.blacklisted_devices.add(connected_device)
raise OpenFailed('Failed to open %s with error: %s'%(
connected_device, as_unicode(e)))
devdata = self.dev.data
storage = [s for s in devdata.get('storage', []) if s.get('rw', False)]
if not storage:
self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,))
self._main_id = storage[0]['id']
if len(storage) > 1:
self._carda_id = storage[1]['id']
if len(storage) > 2:
self._cardb_id = storage[2]['id']
self.current_friendly_name = devdata.get('friendly_name', None)
@synchronous
def get_device_information(self, end_session=True):
d = self.dev.data
dv = d.get('device_version', '')
return (self.current_friendly_name, dv, dv, '')
@synchronous
def card_prefix(self, end_session=True):
ans = [None, None]
if self._carda_id is not None:
ans[0] = 'mtp:::%s:::'%self._carda_id
if self._cardb_id is not None:
ans[1] = 'mtp:::%s:::'%self._cardb_id
return tuple(ans)
@synchronous
def total_space(self, end_session=True):
ans = [0, 0, 0]
dd = self.dev.data
for s in dd.get('storage', []):
i = {self._main_id:0, self._carda_id:1,
self._cardb_id:2}.get(s.get('id', -1), None)
if i is not None:
ans[i] = s['capacity']
return tuple(ans)
@synchronous
def free_space(self, end_session=True):
self.dev.update_data()
ans = [0, 0, 0]
dd = self.dev.data
for s in dd.get('storage', []):
i = {self._main_id:0, self._carda_id:1,
self._cardb_id:2}.get(s.get('id', -1), None)
if i is not None:
ans[i] = s['free_space']
return tuple(ans)

View file

@ -34,6 +34,18 @@ typedef struct {
} ClientInfo;
extern ClientInfo client_info;
// Device type
typedef struct {
PyObject_HEAD
// Type-specific fields go here.
wchar_t *pnp_id;
IPortableDeviceValues *client_information;
IPortableDevice *device;
PyObject *device_information;
} Device;
extern PyTypeObject DeviceType;
// Utility functions
PyObject *hresult_set_exc(const char *msg, HRESULT hr);
wchar_t *unicode_to_wchar(PyObject *o);

View file

@ -65,6 +65,10 @@ def main():
pnp_id = dev.detect_managed_devices(devices)
# pprint.pprint(dev.detected_devices)
print ('Trying to connect to:', pnp_id)
dev.open(pnp_id, '')
print ('Connected to:', dev.get_gui_name())
print ('Total space', dev.total_space())
print ('Free space', dev.free_space())
finally:
dev.shutdown()

View file

@ -186,6 +186,10 @@ PyMODINIT_FUNC
initwpd(void) {
PyObject *m;
wpd::DeviceType.tp_new = PyType_GenericNew;
if (PyType_Ready(&wpd::DeviceType) < 0)
return;
m = Py_InitModule3("wpd", wpd_methods, "Interface to the WPD windows service.");
if (m == NULL) return;
@ -194,6 +198,10 @@ initwpd(void) {
NoWPD = PyErr_NewException("wpd.NoWPD", NULL, NULL);
if (NoWPD == NULL) return;
Py_INCREF(&DeviceType);
PyModule_AddObject(m, "Device", (PyObject *)&DeviceType);
}