mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-05-03 06:26:21 +02:00
WPD: Implement opening device and getting basic device info.
This commit is contained in:
parent
ba22a7a5ca
commit
585b9a5fea
8 changed files with 257 additions and 17 deletions
|
|
@ -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=[
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:")
|
||||
|
|
|
|||
137
src/calibre/devices/mtp/windows/device.cpp
Normal file
137
src/calibre/devices/mtp/windows/device.cpp
Normal 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 */
|
||||
}; // }}}
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue