diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 7c43ab658f..6dad114a6f 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -24,6 +24,7 @@ isosx = 'darwin' in sys.platform.lower() isnewosx = isosx and getattr(sys, 'new_app_bundle', False) islinux = not(iswindows or isosx) isfrozen = hasattr(sys, 'frozen') +isunix = isosx or islinux try: preferred_encoding = locale.getpreferredencoding() diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index 96226fcfd6..2cfeee4cbd 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -10,7 +10,7 @@ Device driver for Bookeen's Cybook Gen 3 import os -from calibre import islinux +from calibre.constants import isunix from calibre.devices.usbms.driver import USBMS import calibre.devices.cybookg3.t2b as t2b @@ -55,7 +55,7 @@ class CYBOOKG3(USBMS): @classmethod def can_handle(cls, device_info, debug=False): - if islinux: + if isunix: return device_info[3] == 'Bookeen' and device_info[4] == 'Cybook Gen3' return True @@ -87,6 +87,6 @@ class CYBOOK_OPUS(CYBOOKG3): @classmethod def can_handle(cls, device_info, debug=False): - if islinux: + if isunix: return device_info[3] == 'Bookeen' return True diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 3eb7a44853..8ce94d43b3 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -104,12 +104,12 @@ class DevicePlugin(Plugin): @classmethod def is_usb_connected(cls, devices_on_system, debug=False): ''' - Return True if a device handled by this plugin is currently connected. + Return True, device_info if a device handled by this plugin is currently connected. :param devices_on_system: List of devices currently connected ''' if iswindows: - return cls.is_usb_connected_windows(devices_on_system, debug=debug) + return cls.is_usb_connected_windows(devices_on_system, debug=debug), None vendors_on_system = set([x[0] for x in devices_on_system]) vendors = cls.VENDOR_ID if hasattr(cls.VENDOR_ID, '__len__') else [cls.VENDOR_ID] @@ -134,18 +134,20 @@ class DevicePlugin(Plugin): if debug: cls.print_usb_device_info(dev) if cls.can_handle(dev, debug=debug): - return True - return False + return True, dev + return False, None - def reset(self, key='-1', log_packets=False, report_progress=None) : + def reset(self, key='-1', log_packets=False, report_progress=None, + detected_device=None) : """ - @param key: The key to unlock the device - @param log_packets: If true the packet stream to/from the device is logged - @param report_progress: Function that is called with a % progress + :key: The key to unlock the device + :log_packets: If true the packet stream to/from the device is logged + :report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks If it is called with -1 that means that the task does not have any progress information + :detected_device: Device information from the device scanner """ raise NotImplementedError() diff --git a/src/calibre/devices/prs500/cli/main.py b/src/calibre/devices/prs500/cli/main.py index 01685d3ac7..a49bca4794 100755 --- a/src/calibre/devices/prs500/cli/main.py +++ b/src/calibre/devices/prs500/cli/main.py @@ -206,9 +206,10 @@ def main(): scanner.scan() connected_devices = [] for d in device_plugins(): - if scanner.is_device_connected(d): + ok, det = scanner.is_device_connected(d) + if ok: dev = d - dev.reset(log_packets=options.log_packets) + dev.reset(log_packets=options.log_packets, detected_device=det) connected_devices.append(dev) if dev is None: diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 8d2c4cc9d4..c822b92162 100644 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -194,7 +194,8 @@ class PRS500(DeviceConfig, DevicePlugin): return run_session - def reset(self, key='-1', log_packets=False, report_progress=None) : + def reset(self, key='-1', log_packets=False, report_progress=None, + detected_device=None) : """ @param key: The key to unlock the device @param log_packets: If true the packet stream to/from the device is logged diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index d026904a35..b1f748835c 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -22,9 +22,32 @@ from itertools import repeat from calibre.devices.interface import DevicePlugin from calibre.devices.errors import DeviceError, FreeSpaceError from calibre.devices.usbms.deviceconfig import DeviceConfig -from calibre import iswindows, islinux, isosx, __appname__ +from calibre.constants import iswindows, islinux, isosx, __appname__, plugins from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to +if isosx: + usbobserver, usbobserver_err = plugins['usbobserver'] + +class USBDevice: + + def __init__(self, dev): + self.idVendor = dev[0] + self.idProduct = dev[1] + self.bcdDevice = dev[2] + self.manufacturer = dev[3] + self.product = dev[4] + self.serial = dev[5] + + def match_serial(self, serial): + return self.serial and self.serial == serial + + def match_numbers(self, vid, pid, bcd): + return self.idVendor == vid and self.idProduct == pid and self.bcdDevice == bcd + + def match_strings(self, vid, pid, bcd, man, prod): + return self.match_numbers(vid, pid, bcd) and \ + self.manufacturer == man and self.product == prod + class Device(DeviceConfig, DevicePlugin): ''' @@ -108,8 +131,10 @@ class Device(DeviceConfig, DevicePlugin): FDI_LUNS = {'lun0':0, 'lun1':1, 'lun2':2} FDI_BCD_TEMPLATE = '' - def reset(self, key='-1', log_packets=False, report_progress=None) : + def reset(self, key='-1', log_packets=False, report_progress=None, + detected_device=None): self._main_prefix = self._card_a_prefix = self._card_b_prefix = None + self.detected_device = USBDevice(detected_device) @classmethod def get_gui_name(cls): @@ -391,29 +416,80 @@ class Device(DeviceConfig, DevicePlugin): raise time.sleep(2) - def open_osx(self): - mount = self.osx_run_mount() - names = self.get_osx_mountpoints() - dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+' - if 'main' not in names.keys(): - raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) - main_pat = dev_pat % names['main'] - main_match = re.search(main_pat, mount) - if main_match is None: - raise DeviceError(_('Unable to detect the %s mount point. Try rebooting.')%self.__class__.__name__) - self._main_prefix = main_match.group(2) + os.sep - card_a_pat = names['carda'] if 'carda' in names.keys() else None - card_b_pat = names['cardb'] if 'cardb' in names.keys() else None - - def get_card_prefix(pat): - if pat is not None: - pat = dev_pat % pat - return re.search(pat, mount).group(2) + os.sep + def _osx_bsd_names(self): + if usbobserver_err: + raise RuntimeError('Failed to load usbobserver: '+usbobserver_err) + drives = usbobserver.get_usb_drives() + matches = [] + d = self.detected_device + if d.serial: + for path, vid, pid, bcd, ven, prod, serial in drives: + if d.match_serial(serial): + matches.append(path) + if not matches: + if d.manufacturer and d.product: + for path, vid, pid, bcd, man, prod, serial in drives: + if d.match_strings(vid, pid, bcd, man, prod): + matches.append(path) else: - return None + for path, vid, pid, bcd, man, prod, serial in drives: + if d.match_numbers(vid, pid, bcd): + matches.append(path) + if not matches: + raise DeviceError( + 'Could not detect BSD names for %s. Try rebooting.' % self.name) - self._card_a_prefix = get_card_prefix(card_a_pat) - self._card_b_prefix = get_card_prefix(card_b_pat) + pat = re.compile(r'(?P\d+)([a-z]+(?P

\d+)){0,1}') + def nums(x): + m = pat.search(x) + if m is None: + return (10000, 0) + g = m.groupdict() + if g['p'] is None: + g['p'] = 0 + return map(int, (g.get('m'), g.get('p'))) + + def dcmp(x, y): + x = x.rpartition('/')[-1] + y = y.rpartition('/')[-1] + x, y = nums(x), nums(y) + ans = cmp(x[0], y[0]) + if ans == 0: + ans = cmp(x[1], y[1]) + return ans + + matches.sort(cmp=dcmp) + drives = {'main':matches[0]} + if len(matches > 1): + drives['carda'] = matches[1] + if len(matches > 2): + drives['cardb'] = matches[2] + + return drives + + def osx_bsd_names(self): + try: + return self._osx_bsd_names() + except: + time.sleep(2) + return self._osx_bsd_names() + + def open_osx(self): + drives = self.osx_bsd_names() + drives = self.osx_sort_names(drives) + mount_map = usbobserver.get_mounted_filesystems() + for k, v in drives.items(): + drives[k] = mount_map.get(k, None) + if drives['main'] is None: + raise DeviceError(_('Unable to detect the %s mount point. Try rebooting.')%self.__class__.__name__) + self._main_prefix = drives['main']+os.sep + def get_card_prefix(c): + ans = drives.get(c, None) + if ans is not None: + ans += os.sep + return ans + self._card_a_prefix = get_card_prefix('carda') + self._card_b_prefix = get_card_prefix('cardb') def find_device_nodes(self): diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 7556ff7dcf..499c464937 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -33,10 +33,6 @@ class USBMS(CLI, Device): FORMATS = [] CAN_SET_METADATA = False - def reset(self, key='-1', log_packets=False, report_progress=None): - Device.reset(self, key=key, log_packets=log_packets, - report_progress=report_progress) - def get_device_information(self, end_session=True): self.report_progress(1.0, _('Get device information...')) return (self.__class__.__name__, '', '', '') diff --git a/src/calibre/devices/usbobserver/usbobserver.c b/src/calibre/devices/usbobserver/usbobserver.c index 8303f7e061..b58e7416cd 100644 --- a/src/calibre/devices/usbobserver/usbobserver.c +++ b/src/calibre/devices/usbobserver/usbobserver.c @@ -268,7 +268,7 @@ usbobserver_get_mounted_filesystems(PyObject *self, PyObject *args) { buf = (struct statfs*)calloc(num, sizeof(struct statfs)); if (buf == NULL) return PyErr_NoMemory(); - num = getfsstat(buf, num*sizeof(struct statfs), MNT_WAIT); + num = getfsstat(buf, num*sizeof(struct statfs), MNT_NOWAIT); if (num == -1) { PyErr_SetString(PyExc_RuntimeError, "Call to getfsstat failed"); return NULL; diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 4471f285dc..5720d87b5e 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -94,8 +94,8 @@ class DeviceManager(Thread): import pythoncom pythoncom.CoInitialize() try: - for dev in connected_devices: - dev.reset() + for dev, detected_device in connected_devices: + dev.reset(detected_device=detected_device) try: dev.open() except: @@ -116,10 +116,10 @@ class DeviceManager(Thread): self.scanner.scan() connected_devices = [] for device in self.devices: - connected = self.scanner.is_device_connected(device[0]) + connected, detected_device = self.scanner.is_device_connected(device[0]) if connected and not device[1] and not device[2]: # If connected and not showing in GUI and not ejected - connected_devices.append(device[0]) + connected_devices.append((device[0], detected_device)) device[1] = True elif not connected and device[1]: # Disconnected but showing in GUI diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index a5c530c38b..ad8f0082b1 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -190,10 +190,14 @@ class RecursiveFetcher(object): time.sleep(delta) if isinstance(url, unicode): url = url.encode('utf-8') - purl = list(urlparse.urlparse(url)) - for i in range(2, 6): - purl[i] = quote(purl[i]) - url = urlparse.urlunparse(purl) + # Not sure is this is really needed as I think mechanize + # handles quoting automatically, but leaving it in + # in case it breaks something + if re.search(r'\s+|,', url) is not None: + purl = list(urlparse.urlparse(url)) + for i in range(2, 6): + purl[i] = quote(purl[i]) + url = urlparse.urlunparse(purl) try: open_func = getattr(self.browser, 'open_novisit', self.browser.open) with closing(open_func(url, timeout=self.timeout)) as f: