diff --git a/setup/extensions.py b/setup/extensions.py index 44a07a34c6..b51c8c7aaa 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -298,7 +298,8 @@ class Build(Command): self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects') if not os.path.exists(self.obj_dir): os.makedirs(self.obj_dir) - self.build_style(self.j(self.SRC, 'calibre', 'plugins')) + if not opts.only: + self.build_style(self.j(self.SRC, 'calibre', 'plugins')) for ext in extensions: if opts.only != 'all' and opts.only != ext.name: continue diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py new file mode 100644 index 0000000000..3d98155efe --- /dev/null +++ b/src/calibre/devices/mtp/windows/driver.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import time +from threading import RLock + +from calibre import as_unicode, prints +from calibre.constants import plugins, __appname__, numeric_version +from calibre.devices.mtp.base import MTPDeviceBase, synchronous + +class MTP_DEVICE(MTPDeviceBase): + + supported_platforms = ['windows'] + + def __init__(self, *args, **kwargs): + MTPDeviceBase.__init__(self, *args, **kwargs) + self.dev = None + self.lock = RLock() + self.blacklisted_devices = set() + self.ejected_devices = set() + self.currently_connected_pnp_id = None + self.detected_devices = {} + self.previous_devices_on_system = frozenset() + self.last_refresh_devices_time = time.time() + self.wpd = self.wpd_error = None + + @synchronous + def startup(self): + self.wpd, self.wpd_error = plugins['wpd'] + if self.wpd is not None: + try: + self.wpd.init(__appname__, *(numeric_version[:3])) + except self.wpd.NoWPD: + self.wpd_error = _( + 'The Windows Portable Devices service is not available' + ' on your computer. You may need to install Windows' + ' Media Player 11 or newer and/or restart your computer') + except Exception as e: + self.wpd_error = as_unicode(e) + + @synchronous + def shutdown(self): + if self.wpd is not None: + self.wpd.uninit() + + @synchronous + def detect_managed_devices(self, devices_on_system): + if self.wpd is None: return None + + devices_on_system = frozenset(devices_on_system) + if (devices_on_system != self.previous_devices_on_system or time.time() + - self.last_refresh_devices_time > 10): + self.previous_devices_on_system = devices_on_system + self.last_refresh_devices_time = time.time() + try: + pnp_ids = frozenset(self.wpd.enumerate_devices()) + except: + return None + + self.detected_devices = {dev:self.detected_devices.get(dev, None) + for dev in pnp_ids} + + # Get device data for detected devices. If there is an error, we will + # try again for that device the next time this method is called. + for dev in tuple(self.detected_devices.iterkeys()): + data = self.detected_devices.get(dev, None) + if data is None or data is False: + try: + data = self.wpd.device_info(dev) + except Exception as e: + prints('Failed to get device info for device:', dev, + 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) + + if self.currently_connected_pnp_id is not None: + return (self.currently_connected_pnp_id if + self.currently_connected_pnp_id in self.detected_devices + else None) + + for dev, data in self.detected_devices.iteritems(): + if dev in self.blacklisted_devices or dev in self.ejected_devices: + # Ignore blacklisted and ejected devices + continue + if data and self.is_suitable_wpd_device(data): + return dev + + return None + + def is_suitable_wpd_device(self, devdata): + # Check that protocol is MTP + protocol = devdata.get('protocol', '').lower() + if not protocol.startswith('mtp:'): return False + + # Check that the device has some read-write storage + if not devdata.get('has_storage', False): return False + has_rw_storage = False + for s in devdata.get('storage', []): + if s.get('rw', False): + has_rw_storage = True + break + if not has_rw_storage: return False + + return True + + @synchronous + def post_yank_cleanup(self): + self.currently_connected_pnp_id = 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 + + diff --git a/src/calibre/devices/mtp/windows/remote.py b/src/calibre/devices/mtp/windows/remote.py index 7c16a87ed1..4830a8e4ec 100644 --- a/src/calibre/devices/mtp/windows/remote.py +++ b/src/calibre/devices/mtp/windows/remote.py @@ -7,78 +7,67 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import subprocess, sys, os, pprint +import subprocess, sys, os, pprint, signal, time, glob pprint def build(mod='wpd'): - builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split()) - syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split()) - if builder.wait() != 0: - raise Exception('Failed to build plugin') - if syncer.wait() != 0: - raise Exception('Failed to rsync to getafix') - subprocess.check_call( - ('scp xp_build:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split()) - subprocess.check_call( - ('scp /tmp/%s.pyd getafix:calibre/src/calibre/devices/mtp/windows'%mod).split()) - p = subprocess.Popen( - 'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split()) - p.wait() - print() - + master = subprocess.Popen('ssh -MN getafix'.split()) + master2 = subprocess.Popen('ssh -MN xp_build'.split()) + try: + while not glob.glob(os.path.expanduser('~/.ssh/*kovid@xp_build*')): + time.sleep(0.05) + builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split()) + if builder.wait() != 0: + raise Exception('Failed to build plugin') + while not glob.glob(os.path.expanduser('~/.ssh/*kovid@getafix*')): + time.sleep(0.05) + syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split()) + if syncer.wait() != 0: + raise Exception('Failed to rsync to getafix') + subprocess.check_call( + ('scp xp_build:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split()) + subprocess.check_call( + ('scp /tmp/%s.pyd getafix:calibre/src/calibre/devices/mtp/windows'%mod).split()) + p = subprocess.Popen( + 'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split()) + p.wait() + print() + finally: + for m in (master2, master): + m.send_signal(signal.SIGHUP) + for m in (master2, master): + m.wait() def main(): - sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + fp, d = os.path.abspath(__file__), os.path.dirname + if b'CALIBRE_DEVELOP_FROM' not in os.environ: + env = os.environ.copy() + env[b'CALIBRE_DEVELOP_FROM'] = bytes(d(d(d(d(d(fp)))))) + subprocess.call(['calibre-debug', '-e', fp], env=env) + return + + sys.path.insert(0, os.path.dirname(fp)) + if 'wpd' in sys.modules: + del sys.modules['wpd'] import wpd from calibre.constants import plugins plugins._plugins['wpd'] = (wpd, '') sys.path.pop(0) - wpd.init('calibre', 1, 0, 0) + + from calibre.devices.scanner import win_scanner + from calibre.devices.mtp.windows.driver import MTP_DEVICE + dev = MTP_DEVICE(None) + dev.startup() + print (dev.wpd, dev.wpd_error) + try: - for pnp_id in wpd.enumerate_devices(): - print (pnp_id) - pprint.pprint(wpd.device_info(pnp_id)) + devices = win_scanner() + pnp_id = dev.detect_managed_devices(devices) + # pprint.pprint(dev.detected_devices) + print ('Trying to connect to:', pnp_id) finally: - wpd.uninit() - -def winutil(): - sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - del sys.modules['winutil'] - import winutil - from calibre.constants import plugins - plugins._plugins['winutil'] = (winutil, '') - sys.path.pop(0) - print (winutil.serial_number_from_drive('F')) - -def get_subkeys(key): - import _winreg - index = -1 - while True: - index += 1 - try: - yield _winreg.EnumKey(key, index) - except OSError: - break - -def get_values(key): - import _winreg - index = -1 - while True: - index +=1 - try: - yield _winreg.EnumValue(key, index) - except OSError: - break - -def test(): - vid, pid = 0x1949, 0x4 - import _winreg as r - usb = r.OpenKey(r.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Enum\\USB') - q = ('vid_%4.4x&pid_%4.4x'%(vid, pid)) - dev = r.OpenKey(usb, q) - print (list(get_subkeys(dev))) + dev.shutdown() if __name__ == '__main__': main() - # winutil() diff --git a/src/calibre/devices/mtp/windows/wpd.cpp b/src/calibre/devices/mtp/windows/wpd.cpp index e739826fbb..4d7cbc310d 100644 --- a/src/calibre/devices/mtp/windows/wpd.cpp +++ b/src/calibre/devices/mtp/windows/wpd.cpp @@ -92,14 +92,10 @@ wpd_enumerate_devices(PyObject *self, PyObject *args) { ENSURE_WPD(NULL); - if (!PyArg_ParseTuple(args, "|O", &refresh)) return NULL; - - if (refresh != NULL && PyObject_IsTrue(refresh)) { - Py_BEGIN_ALLOW_THREADS; - hr = portable_device_manager->RefreshDeviceList(); - Py_END_ALLOW_THREADS; - if (FAILED(hr)) return hresult_set_exc("Failed to refresh the list of portable devices", hr); - } + Py_BEGIN_ALLOW_THREADS; + hr = portable_device_manager->RefreshDeviceList(); + Py_END_ALLOW_THREADS; + if (FAILED(hr)) return hresult_set_exc("Failed to refresh the list of portable devices", hr); hr = portable_device_manager->GetDevices(NULL, &num_of_devices); num_of_devices += 15; // Incase new devices were connected between this call and the next @@ -175,7 +171,7 @@ static PyMethodDef wpd_methods[] = { }, {"enumerate_devices", wpd_enumerate_devices, METH_VARARGS, - "enumerate_devices(refresh=False)\n\n Get the list of device PnP ids for all connected devices recognized by the WPD service. The result is cached, unless refresh=True. Do not call with refresh=True too often as it is resource intensive." + "enumerate_devices()\n\n Get the list of device PnP ids for all connected devices recognized by the WPD service. Do not call too often as it is resource intensive." }, {"device_info", wpd_device_info, METH_VARARGS,