def open_linux(self): def mount(node, type): mp = self.node_mountpoint(node) if mp is not None: return mp, 0 def do_mount(node): try: from calibre.devices.udisks import mount mount(node) return 0 except: print 'Udisks mount call failed:' import traceback traceback.print_exc() return 1 ret = do_mount(node) if ret != 0: return None, ret return self.node_mountpoint(node) + '/', 0 main, carda, cardb = self.find_device_nodes() if main is None: raise DeviceError( _('Unable to detect the %s disk drive. Either ' 'the device has already been ejected, or your ' 'kernel is exporting a deprecated version of SYSFS.') % self.__class__.__name__) self._linux_mount_map = {} mp, ret = mount(main, 'main') if mp is None: raise DeviceError( _('Unable to mount main memory (Error code: %d)') % ret) if not mp.endswith('/'): mp += '/' self._linux_mount_map[main] = mp self._main_prefix = mp self._linux_main_device_node = main cards = [(carda, '_card_a_prefix', 'carda'), (cardb, '_card_b_prefix', 'cardb')] for card, prefix, typ in cards: if card is None: continue mp, ret = mount(card, typ) if mp is None: print >> sys.stderr, 'Unable to mount card (Error code: %d)' % ret else: if not mp.endswith('/'): mp += '/' setattr(self, prefix, mp) self._linux_mount_map[card] = mp self.filter_read_only_mount_points()
def open_osx(self): drives = self.osx_bsd_names() bsd_drives = dict(**drives) drives = self.osx_sort_names(drives) mount_map = usbobserver.get_mounted_filesystems() for k, v in drives.items(): drives[k] = mount_map.get(v, None) if drives['main'] is None: print bsd_drives, mount_map, drives raise DeviceError( _('Unable to detect the %s mount point. Try rebooting.') % self.__class__.__name__) pat = self.OSX_MAIN_MEM_VOL_PAT if pat is not None and len(drives) > 1 and 'main' in drives: if pat.search(drives['main']) is None: main = drives['main'] for x in ('carda', 'cardb'): if x in drives and pat.search(drives[x]): drives['main'] = drives.pop(x) drives[x] = main break 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 sanity_check(on_card, files, card_prefixes, free_space): if on_card == 'carda' and not card_prefixes[0]: raise WrongDestinationError( _('The reader has no storage card %s. You may have changed ' 'the default send to device action. Right click on the "Send ' 'to device" button and reset the default action to be ' '"Send to main memory".') % 'A') elif on_card == 'cardb' and not card_prefixes[1]: raise WrongDestinationError( _('The reader has no storage card %s. You may have changed ' 'the default send to device action. Right click on the "Send ' 'to device" button and reset the default action to be ' '"Send to main memory".') % 'B') elif on_card and on_card not in ('carda', 'cardb'): raise DeviceError(_('Selected slot: %s is not supported.') % on_card) size = 0 for f in files: size += os.path.getsize(getattr(f, 'name', f)) if not on_card and size > free_space[0] - 2 * 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space in main memory")) if on_card == 'carda' and size > free_space[1] - 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space on the storage card")) if on_card == 'cardb' and size > free_space[2] - 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space on the storage card"))
def put_file(self, parent, name, stream, size, callback=None, replace=True): e = parent.folder_named(name) if e is not None: raise ValueError( 'Cannot upload file, %s already has a folder named: %s' % (parent.full_path, e.name)) e = parent.file_named(name) if e is not None: if not replace: raise ValueError('Cannot upload file %s, it already exists' % (e.full_path, )) self.delete_file_or_folder(e) sid, pid = parent.storage_id, parent.object_id if pid == sid: pid = 0xFFFFFFFF ans, errs = self.dev.put_file(sid, pid, name, stream, size, callback) if ans is None: raise DeviceError( 'Failed to upload file named: %s to %s: %s' % (name, parent.full_path, self.format_errorstack(errs))) return parent.add_child(ans)
def remove_orphaned_records(self, connection, dbpath): try: cursor = connection.cursor() debug_print("Removing Orphaned Collection Records") # Purge any collections references that point into the abyss query = 'DELETE FROM booktags WHERE book_id NOT IN (SELECT _id FROM books)' cursor.execute(query) query = 'DELETE FROM booktags WHERE tag_id NOT IN (SELECT _id FROM tags)' cursor.execute(query) debug_print("Removing Orphaned Book Records") cursor.close() except Exception: import traceback tb = traceback.format_exc() raise DeviceError((('The Paladin database is corrupted. ' ' Delete the file %s on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' ' any notes/highlights, etc.')%dbpath)+' Underlying error:' '\n'+tb)
def _osx_bsd_names(self): drives = self.osx_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: 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) pat = re.compile(r'(?P<m>\d+)([a-z]+(?P<p>\d+)){0,1}') def nums(x): 'Return (disk num, partition number)' m = pat.search(x) if m is None: return (10000, -1) g = m.groupdict() if g['p'] is None: g['p'] = 0 return map(int, (g.get('m'), g.get('p'))) def dcmp(x, y): ''' Sorting based on the following scheme: - disks without partitions are first - sub sorted based on disk number - disks with partitions are sorted first on disk number, then on partition number ''' x = x.rpartition('/')[-1] y = y.rpartition('/')[-1] x, y = nums(x), nums(y) if x[1] == 0 and y[1] > 0: return cmp(1, 2) if x[1] > 0 and y[1] == 0: return cmp(2, 1) 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): drives = self.osx_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 and 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) if not matches: # Since Apple started mangling the names stored in the IOKit # registry, we cannot trust match_strings() so fallback to matching # on just numbers. See http://www.mobileread.com/forums/showthread.php?t=273213 for path, vid, pid, bcd, man, prod, serial in drives: if d.match_numbers(vid, pid, bcd): matches.append(path) if not matches: from pprint import pformat raise DeviceError( f'Could not detect BSD names for {self.name}. Try rebooting.\nOutput from osx_get_usb_drives():\n{pformat(drives)}' ) pat = re.compile(r'(?P<m>\d+)([a-z]+(?P<p>\d+)){0,1}') def nums(x): 'Return (disk num, partition number)' m = pat.search(x) if m is None: return (10000, -1) g = m.groupdict() if g['p'] is None: g['p'] = 0 return list(map(int, (g.get('m'), g.get('p')))) def cmp_key(x): ''' Sorting based on the following scheme: - disks without partitions are first - sub sorted based on disk number - disks with partitions are sorted first on disk number, then on partition number ''' x = x.rpartition('/')[-1] disk_num, part_num = nums(x) has_part = 1 if part_num > 0 else 0 return has_part, disk_num, part_num matches.sort(key=cmp_key) drives = {'main': matches[0]} if len(matches) > 1: drives['carda'] = matches[1] if len(matches) > 2: drives['cardb'] = matches[2] return drives
def open_windows(self): from calibre.devices.scanner import drive_is_ok from calibre.devices.winusb import get_drive_letters_for_device usbdev = self.device_being_opened debug = DEBUG or getattr(self, 'do_device_debug', False) try: dlmap = get_drive_letters_for_device(usbdev, debug=debug) except Exception: dlmap = {} if not dlmap.get('drive_letters'): time.sleep(7) dlmap = get_drive_letters_for_device(usbdev, debug=debug) if debug: from pprint import pformat prints(f'Drive letters for {usbdev}') prints(pformat(dlmap)) filtered = set() for dl in dlmap['drive_letters']: pnp_id = dlmap['pnp_id_map'][dl].upper() if dl in dlmap['readonly_drives']: filtered.add(dl) if debug: prints('Ignoring the drive %s as it is readonly' % dl) elif self.windows_filter_pnp_id(pnp_id): filtered.add(dl) if debug: prints( f'Ignoring the drive {dl} because of a PNP filter on {pnp_id}' ) elif not drive_is_ok(dl, debug=debug): filtered.add(dl) if debug: prints( 'Ignoring the drive %s because failed to get free space for it' % dl) dlmap['drive_letters'] = [ dl for dl in dlmap['drive_letters'] if dl not in filtered ] if not dlmap['drive_letters']: raise DeviceError( _('Unable to detect any disk drives for the device: %s. Try rebooting' ) % self.get_gui_name()) drives = {} for drive_letter, which in zip(dlmap['drive_letters'], 'main carda cardb'.split()): drives[which] = drive_letter + ':\\' drives = self.windows_sort_drives(drives) self._main_prefix = drives.get('main') self._card_a_prefix = drives.get('carda', None) self._card_b_prefix = drives.get('cardb', None)
def open_freebsd(self): import dbus # There should be some way to access the -v arg... verbose = False # this gives us access to the S/N, etc. of the reader that the scanner has found # and the match routines for some of that data, like s/n, vendor ID, etc. d=self.detected_device if not d.serial: raise DeviceError("Device has no S/N. Can't continue") return False vols=[] bus = dbus.SystemBus() manager = dbus.Interface(bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager'), 'org.freedesktop.Hal.Manager') paths = manager.FindDeviceStringMatch('usb.serial',d.serial) for path in paths: objif = dbus.Interface(bus.get_object('org.freedesktop.Hal', path), 'org.freedesktop.Hal.Device') # Extra paranoia... try: if d.idVendor == objif.GetProperty('usb.vendor_id') and \ d.idProduct == objif.GetProperty('usb.product_id') and \ d.manufacturer == objif.GetProperty('usb.vendor') and \ d.product == objif.GetProperty('usb.product') and \ d.serial == objif.GetProperty('usb.serial'): dpaths = manager.FindDeviceStringMatch('storage.originating_device', path) for dpath in dpaths: #devif = dbus.Interface(bus.get_object('org.freedesktop.Hal', dpath), 'org.freedesktop.Hal.Device') try: vpaths = manager.FindDeviceStringMatch('block.storage_device', dpath) for vpath in vpaths: try: vdevif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device') if not vdevif.GetProperty('block.is_volume'): continue if vdevif.GetProperty('volume.fsusage') != 'filesystem': continue volif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device.Volume') pdevif = dbus.Interface(bus.get_object('org.freedesktop.Hal', vdevif.GetProperty('info.parent')), 'org.freedesktop.Hal.Device') vol = {'node': pdevif.GetProperty('block.device'), 'dev': vdevif, 'vol': volif, 'label': vdevif.GetProperty('volume.label')} vols.append(vol) except dbus.exceptions.DBusException, e: print e continue except dbus.exceptions.DBusException, e: print e continue except dbus.exceptions.DBusException, e: continue
def open_osx(self): from calibre_extensions.usbobserver import get_mounted_filesystems bsd_drives = self.osx_bsd_names() drives = self.osx_sort_names(bsd_drives.copy()) mount_map = get_mounted_filesystems() # macOS 13 Ventura uses a weird scheme for mounted FAT devices of the # form fat://basename_of_bsd_name/basename_of_mountpoint # see https://www.mobileread.com/forums/showthread.php?t=347294 for dev_node in tuple(mount_map): if ':' in dev_node and '//' in dev_node: val = mount_map[dev_node] dev_node = dev_node.split('/')[-2] dev_node = f'/dev/{dev_node}' if dev_node not in mount_map: mount_map[dev_node] = val drives = {k: mount_map.get(v) for k, v in iteritems(drives)} if is_debugging(): print() from pprint import pprint pprint({ 'bsd_drives': bsd_drives, 'mount_map': mount_map, 'drives': drives }) if drives.get('carda') is None and drives.get('cardb') is not None: drives['carda'] = drives.pop('cardb') if drives.get('main') is None and drives.get('carda') is not None: drives['main'] = drives.pop('carda') if drives.get('carda') is None and drives.get('cardb') is not None: drives['carda'] = drives.pop('cardb') if drives.get('main') is None: raise DeviceError( _('Unable to detect the %s mount point. Try rebooting.') % self.__class__.__name__) pat = self.OSX_MAIN_MEM_VOL_PAT if pat is not None and len(drives) > 1 and 'main' in drives: if pat.search(drives['main']) is None: main = drives['main'] for x in ('carda', 'cardb'): if x in drives and pat.search(drives[x]): drives['main'] = drives.pop(x) drives[x] = main break 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 open_freebsd(self): # There should be some way to access the -v arg... verbose = False # this gives us access to the S/N, etc. of the reader that the scanner has found # and the match routines for some of that data, like s/n, vendor ID, etc. d = self.detected_device if not d.serial: raise DeviceError("Device has no S/N. Can't continue") from .hal import get_hal hal = get_hal() vols = hal.get_volumes(d) if verbose: print("FBSD: ", vols) ok, mv = hal.mount_volumes(vols) if not ok: raise DeviceError(_('Unable to mount the device')) for k, v in mv.items(): setattr(self, k, v)
def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: raise ValueError('%s if a folder'%(f.full_path,)) set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') ok, errs = self.dev.get_file(f.object_id, stream, callback) if not ok: raise DeviceError('Failed to get file: %s with errors: %s'%( f.full_path, self.format_errorstack(errs))) stream.seek(0) if set_name: stream.name = f.name return stream
def remove_orphaned_records(self, connection, dbpath): from sqlite3 import DatabaseError try: cursor = connection.cursor() debug_print("Removing Orphaned Collection Records") # Purge any collections references that point into the abyss query = 'DELETE FROM collections WHERE content_id NOT IN (SELECT _id FROM books)' cursor.execute(query) query = 'DELETE FROM collections WHERE collection_id NOT IN (SELECT _id FROM collection)' cursor.execute(query) debug_print("Removing Orphaned Book Records") # Purge any references to books not in this database # Idea is to prevent any spill-over where these wind up applying to some other book query = 'DELETE FROM %s WHERE content_id NOT IN (SELECT _id FROM books)' cursor.execute(query % 'annotation') cursor.execute(query % 'bookmark') cursor.execute(query % 'current_position') cursor.execute(query % 'freehand') cursor.execute(query % 'history') cursor.execute(query % 'layout_cache') cursor.execute(query % 'preference') cursor.close() except DatabaseError: import traceback tb = traceback.format_exc() raise DeviceError(( ('The SONY database is corrupted. ' ' Delete the file %s on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' ' any notes/highlights, etc.') % dbpath) + ' Underlying error:' '\n' + tb) def get_lastrowid(self, cursor): # SQLite3 + Python has a fun issue on 32-bit systems with integer overflows. # Issue a SQL query instead, getting the value as a string, and then converting to a long python int manually. query = 'SELECT last_insert_rowid()' cursor.execute(query) row = cursor.fetchone() return long(row[0])
def create_folder(self, parent, name): if not parent.is_folder: raise ValueError('%s is not a folder' % (parent.full_path, )) e = parent.folder_named(name) if e is not None: return e sid, pid = parent.storage_id, parent.object_id if pid == sid: pid = 0 ans, errs = self.dev.create_folder(sid, pid, name) if ans is None: raise DeviceError( 'Failed to create folder named %s in %s with error: %s' % (name, parent.full_path, self.format_errorstack(errs))) return parent.add_child(ans)
def open_osx(self): from calibre_extensions.usbobserver import get_mounted_filesystems bsd_drives = self.osx_bsd_names() drives = self.osx_sort_names(bsd_drives.copy()) mount_map = get_mounted_filesystems() drives = {k: mount_map.get(v) for k, v in iteritems(drives)} if DEBUG: print() from pprint import pprint pprint({ 'bsd_drives': bsd_drives, 'mount_map': mount_map, 'drives': drives }) if drives.get('carda') is None and drives.get('cardb') is not None: drives['carda'] = drives.pop('cardb') if drives.get('main') is None and drives.get('carda') is not None: drives['main'] = drives.pop('carda') if drives.get('carda') is None and drives.get('cardb') is not None: drives['carda'] = drives.pop('cardb') if drives.get('main') is None: raise DeviceError( _('Unable to detect the %s mount point. Try rebooting.') % self.__class__.__name__) pat = self.OSX_MAIN_MEM_VOL_PAT if pat is not None and len(drives) > 1 and 'main' in drives: if pat.search(drives['main']) is None: main = drives['main'] for x in ('carda', 'cardb'): if x in drives and pat.search(drives[x]): drives['main'] = drives.pop(x) drives[x] = main break 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 _sanity_check(self, on_card, files): if on_card == 'carda' and not self._card_a_prefix: raise ValueError(_('The reader has no storage card in this slot.')) elif on_card == 'cardb' and not self._card_b_prefix: raise ValueError(_('The reader has no storage card in this slot.')) elif on_card and on_card not in ('carda', 'cardb'): raise DeviceError( _('Selected slot: %s is not supported.') % on_card) if on_card == 'carda': path = os.path.join( self._card_a_prefix, *(self.get_carda_ebook_dir(for_upload=True).split('/'))) elif on_card == 'cardb': path = os.path.join(self._card_b_prefix, *(self.EBOOK_DIR_CARD_B.split('/'))) else: candidates = self.get_main_ebook_dir(for_upload=True) if isinstance(candidates, basestring): candidates = [candidates] candidates = [((os.path.join(self._main_prefix, *(x.split('/')))) if x else self._main_prefix) for x in candidates] existing = [x for x in candidates if os.path.exists(x)] if not existing: existing = candidates[:1] path = existing[0] def get_size(obj): path = getattr(obj, 'name', obj) return os.path.getsize(path) sizes = [get_size(f) for f in files] size = sum(sizes) if not on_card and size > self.free_space()[0] - 2 * 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space in main memory")) if on_card == 'carda' and size > self.free_space()[1] - 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space on the storage card")) if on_card == 'cardb' and size > self.free_space()[2] - 1024 * 1024: raise FreeSpaceError( _("There is insufficient free space on the storage card")) return path
def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: raise ValueError('%s if a folder'%(f.full_path,)) set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') try: try: self.dev.get_file(f.object_id, stream, callback) except self.wpd.WPDFileBusy: time.sleep(2) self.dev.get_file(f.object_id, stream, callback) except Exception as e: raise DeviceError('Failed to fetch the file %s with error: %s'% (f.full_path, as_unicode(e))) stream.seek(0) if set_name: stream.name = f.name return stream
def delete_file_or_folder(self, obj): if obj.deleted: return if not obj.can_delete: raise ValueError('Cannot delete %s as deletion not allowed' % (obj.full_path, )) if obj.is_system: raise ValueError('Cannot delete %s as it is a system object' % (obj.full_path, )) if obj.files or obj.folders: raise ValueError('Cannot delete %s as it is not empty' % (obj.full_path, )) parent = obj.parent ok, errs = self.dev.delete_object(obj.object_id) if not ok: raise DeviceError('Failed to delete %s with error: %s' % (obj.full_path, self.format_errorstack(errs))) parent.remove_child(obj) return parent
def filesystem_cache(self): if self._filesystem_cache is None: st = time.time() debug('Loading filesystem metadata...') from calibre.devices.mtp.filesystem_cache import FilesystemCache with self.lock: storage, all_items, all_errs = [], [], [] for sid, capacity in zip( [self._main_id, self._carda_id, self._cardb_id], self.total_space()): if sid is None: continue name = _('Unknown') for x in self.dev.storage_info: if x['id'] == sid: name = x['name'] break storage.append({ 'id': sid, 'size': capacity, 'is_folder': True, 'name': name, 'can_delete': False, 'is_system': True }) self._currently_getting_sid = str(sid) items, errs = self.dev.get_filesystem( sid, partial(self._filesystem_callback, {})) all_items.extend(items), all_errs.extend(errs) if not all_items and all_errs: raise DeviceError( 'Failed to read filesystem from %s with errors: %s' % (self.current_friendly_name, self.format_errorstack(all_errs))) if all_errs: prints('There were some errors while getting the ' ' filesystem from %s: %s' % (self.current_friendly_name, self.format_errorstack(all_errs))) self._filesystem_cache = FilesystemCache(storage, all_items) debug('Filesystem metadata loaded in %g seconds (%d objects)' % (time.time() - st, len(self._filesystem_cache))) return self._filesystem_cache
def filter_read_only_mount_points(self): def is_readonly(mp): if mp is None: return True path = os.path.join(mp, 'calibre_readonly_test') ro = True try: with lopen(path, 'wb'): ro = False except: pass else: try: os.remove(path) except: pass if DEBUG and ro: print('\nThe mountpoint', mp, 'is readonly, ignoring it') return ro for mp in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'): if is_readonly(getattr(self, mp, None)): setattr(self, mp, None) if self._main_prefix is None: for p in ('_card_a_prefix', '_card_b_prefix'): nmp = getattr(self, p, None) if nmp is not None: self._main_prefix = nmp setattr(self, p, None) break if self._main_prefix is None: raise DeviceError( _('The main memory of %s is read only. ' 'This usually happens because of file system errors.') % self.__class__.__name__) if self._card_a_prefix is None and self._card_b_prefix is not None: self._card_a_prefix = self._card_b_prefix self._card_b_prefix = None
def remove_orphaned_records(self, connection, dbpath): from sqlite3 import DatabaseError try: cursor = connection.cursor() debug_print("Removing Orphaned Collection Records") # Purge any collections references that point into the abyss query = 'DELETE FROM collections WHERE content_id NOT IN (SELECT _id FROM books)' cursor.execute(query) query = 'DELETE FROM collections WHERE collection_id NOT IN (SELECT _id FROM collection)' cursor.execute(query) debug_print("Removing Orphaned Book Records") # Purge any references to books not in this database # Idea is to prevent any spill-over where these wind up applying to some other book query = 'DELETE FROM %s WHERE content_id NOT IN (SELECT _id FROM books)' cursor.execute(query % 'annotation') cursor.execute(query % 'bookmark') cursor.execute(query % 'current_position') cursor.execute(query % 'freehand') cursor.execute(query % 'history') cursor.execute(query % 'layout_cache') cursor.execute(query % 'preference') cursor.close() except DatabaseError: import traceback tb = traceback.format_exc() raise DeviceError(( ('The SONY database is corrupted. ' ' Delete the file %s on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' ' any notes/highlights, etc.') % dbpath) + ' Underlying error:' '\n' + tb)
def __init__(self, paths, ext_paths, prefixes, use_author_sort): from lxml import etree if DEBUG: debug_print('Building XMLCache...', paths) self.paths = paths self.prefixes = prefixes self.use_author_sort = use_author_sort # Parse XML files {{{ parser = etree.XMLParser(recover=True) self.roots = {} for source_id, path in paths.items(): if source_id == 0: if not os.path.exists(path): raise DeviceError(('The SONY XML cache %r does not exist. Try' ' disconnecting and reconnecting your reader.')%repr(path)) with lopen(path, 'rb') as f: raw = f.read() else: raw = EMPTY_CARD_CACHE if os.access(path, os.R_OK): with lopen(path, 'rb') as f: raw = f.read() self.roots[source_id] = etree.fromstring(xml_to_unicode( raw, strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0], parser=parser) if self.roots[source_id] is None: raise Exception(('The SONY database at %r is corrupted. Try ' ' disconnecting and reconnecting your reader.')%path) self.ext_paths, self.ext_roots = {}, {} for source_id, path in ext_paths.items(): if not os.path.exists(path): try: with lopen(path, 'wb') as f: f.write(EMPTY_EXT_CACHE) fsync(f) except: pass if os.access(path, os.W_OK): try: with lopen(path, 'rb') as f: self.ext_roots[source_id] = etree.fromstring( xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0], parser=parser) self.ext_paths[source_id] = path except: pass # }}} recs = self.roots[0].xpath('//*[local-name()="records"]') if not recs: raise DeviceError('The SONY XML database is corrupted (no' ' <records>). Try disconnecting an reconnecting' ' your reader.') self.record_roots = {} self.record_roots.update(self.roots) self.record_roots[0] = recs[0] self.detect_namespaces() debug_print('Done building XMLCache...')
def find_device_nodes(self, detected_device=None): def walk(base): base = os.path.abspath(os.path.realpath(base)) for x in os.listdir(base): p = os.path.join(base, x) if os.path.islink(p) or not os.access(p, os.R_OK): continue isfile = os.path.isfile(p) yield p, isfile if not isfile: yield from walk(p) def raw2num(raw): raw = raw.lower() if not raw.startswith('0x'): raw = '0x' + raw return int(raw, 16) # Find device node based on vendor, product and bcd d, j = os.path.dirname, os.path.join usb_dir = None if detected_device is None: detected_device = self.detected_device def test(val, attr): q = getattr(detected_device, attr) return q == val def getnum(usb_dir): def rc(q): with open(j(usb_dir, q), 'rb') as f: return raw2num(f.read().decode('utf-8')) return rc for x, isfile in walk('/sys/devices'): if isfile and x.endswith('idVendor'): usb_dir = d(x) for y in ('idProduct', 'idVendor', 'bcdDevice'): if not os.access(j(usb_dir, y), os.R_OK): usb_dir = None break if usb_dir is None: continue ven, prod, bcd = map(getnum(usb_dir), ('idVendor', 'idProduct', 'bcdDevice')) if not (test(ven, 'idVendor') and test(prod, 'idProduct') and test(bcd, 'bcdDevice')): usb_dir = None continue else: break if usb_dir is None: raise DeviceError( _('Unable to detect the %s disk drive.') % self.__class__.__name__) devnodes, ok = [], {} for x, isfile in walk(usb_dir): if not isfile and '/block/' in x: parts = x.split('/') idx = parts.index('block') if idx == len(parts) - 2: sz = j(x, 'size') node = parts[idx + 1] try: with open(sz, 'rb') as szf: exists = int(szf.read().decode('utf-8')) > 0 if exists: node = self.find_largest_partition(x) ok[node] = True else: ok[node] = False except: ok[node] = False if DEBUG and not ok[node]: print( f'\nIgnoring the node: {node} as could not read size from: {sz}' ) devnodes.append(node) devnodes += list(repeat(None, 3)) ans = ['/dev/' + x if ok.get(x, False) else None for x in devnodes] ans.sort(key=lambda x: x[5:] if x else 'zzzzz') return self.linux_swap_drives(ans[:3])
def open_freebsd(self): import dbus # There should be some way to access the -v arg... verbose = False # this gives us access to the S/N, etc. of the reader that the scanner has found # and the match routines for some of that data, like s/n, vendor ID, etc. d = self.detected_device if not d.serial: raise DeviceError("Device has no S/N. Can't continue") return False vols = [] bus = dbus.SystemBus() manager = dbus.Interface( bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager'), 'org.freedesktop.Hal.Manager') paths = manager.FindDeviceStringMatch('usb.serial', d.serial) for path in paths: objif = dbus.Interface(bus.get_object('org.freedesktop.Hal', path), 'org.freedesktop.Hal.Device') # Extra paranoia... try: if d.idVendor == objif.GetProperty('usb.vendor_id') and \ d.idProduct == objif.GetProperty('usb.product_id') and \ d.manufacturer == objif.GetProperty('usb.vendor') and \ d.product == objif.GetProperty('usb.product') and \ d.serial == objif.GetProperty('usb.serial'): midpath = manager.FindDeviceStringMatch( 'info.parent', path) dpaths = manager.FindDeviceStringMatch( 'storage.originating_device', path) + manager.FindDeviceStringMatch( 'storage.originating_device', midpath[0]) for dpath in dpaths: # devif = dbus.Interface(bus.get_object('org.freedesktop.Hal', dpath), 'org.freedesktop.Hal.Device') try: vpaths = manager.FindDeviceStringMatch( 'block.storage_device', dpath) for vpath in vpaths: try: vdevif = dbus.Interface( bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device') if not vdevif.GetProperty( 'block.is_volume'): continue if vdevif.GetProperty( 'volume.fsusage') != 'filesystem': continue volif = dbus.Interface( bus.get_object('org.freedesktop.Hal', vpath), 'org.freedesktop.Hal.Device.Volume') pdevif = dbus.Interface( bus.get_object( 'org.freedesktop.Hal', vdevif.GetProperty('info.parent')), 'org.freedesktop.Hal.Device') vol = { 'node': pdevif.GetProperty('block.device'), 'dev': vdevif, 'vol': volif, 'label': vdevif.GetProperty('volume.label') } vols.append(vol) except dbus.exceptions.DBusException as e: print(e) continue except dbus.exceptions.DBusException as e: print(e) continue except dbus.exceptions.DBusException as e: continue def ocmp(x, y): if x['node'] < y['node']: return -1 if x['node'] > y['node']: return 1 return 0 vols.sort(cmp=ocmp) if verbose: print("FBSD: ", vols) mtd = 0 for vol in vols: mp = '' if vol['dev'].GetProperty('volume.is_mounted'): mp = vol['dev'].GetProperty('volume.mount_point') else: try: vol['vol'].Mount('Calibre-' + vol['label'], vol['dev'].GetProperty('volume.fstype'), []) loops = 0 while not vol['dev'].GetProperty('volume.is_mounted'): time.sleep(1) loops += 1 if loops > 100: print( "ERROR: Timeout waiting for mount to complete") continue mp = vol['dev'].GetProperty('volume.mount_point') except dbus.exceptions.DBusException as e: print("Failed to mount ", e) continue # Mount Point becomes Mount Path mp += '/' if verbose: print("FBSD: mounted", vol['label'], "on", mp) if mtd == 0: self._main_prefix = mp self._main_vol = vol['vol'] if verbose: print("FBSD: main = ", self._main_prefix) if mtd == 1: self._card_a_prefix = mp self._card_a_vol = vol['vol'] if verbose: print("FBSD: card a = ", self._card_a_prefix) if mtd == 2: self._card_b_prefix = mp self._card_b_vol = vol['vol'] if verbose: print("FBSD: card b = ", self._card_b_prefix) # Note that mtd is used as a bool... not incrementing is fine. break mtd += 1 if mtd > 0: return True raise DeviceError(_('Unable to mount the device'))
def open_windows(self): from calibre.devices.scanner import win_pnp_drives, drivecmp time.sleep(5) drives = {} seen = set() prod_pat = re.compile(r'PROD_(.+?)&') dup_prod_id = False def check_for_dups(pnp_id): try: match = prod_pat.search(pnp_id) if match is not None: prodid = match.group(1) if prodid in seen: return True else: seen.add(prodid) except: pass return False for drive, pnp_id in win_pnp_drives().items(): if self.windows_match_device(pnp_id, 'WINDOWS_CARD_A_MEM') and \ not drives.get('carda', False): drives['carda'] = drive dup_prod_id |= check_for_dups(pnp_id) elif self.windows_match_device(pnp_id, 'WINDOWS_CARD_B_MEM') and \ not drives.get('cardb', False): drives['cardb'] = drive dup_prod_id |= check_for_dups(pnp_id) elif self.windows_match_device(pnp_id, 'WINDOWS_MAIN_MEM') and \ not drives.get('main', False): drives['main'] = drive dup_prod_id |= check_for_dups(pnp_id) if 'main' in drives.keys() and 'carda' in drives.keys() and \ 'cardb' in drives.keys(): break # This is typically needed when the device has the same # WINDOWS_MAIN_MEM and WINDOWS_CARD_A_MEM in which case # if the device is connected without a card, the above # will incorrectly identify the main mem as carda # See for example the driver for the Nook if drives.get('carda', None) is not None and \ drives.get('main', None) is None: drives['main'] = drives.pop('carda') if drives.get('main', None) is None: raise DeviceError( _('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) # Sort drives by their PNP drive numbers if the CARD and MAIN # MEM strings are identical if dup_prod_id or \ self.WINDOWS_MAIN_MEM in (self.WINDOWS_CARD_A_MEM, self.WINDOWS_CARD_B_MEM) or \ self.WINDOWS_CARD_A_MEM == self.WINDOWS_CARD_B_MEM: letters = sorted(drives.values(), cmp=drivecmp) drives = {} for which, letter in zip(['main', 'carda', 'cardb'], letters): drives[which] = letter drives = self.windows_sort_drives(drives) self._main_prefix = drives.get('main') self._card_a_prefix = drives.get('carda', None) self._card_b_prefix = drives.get('cardb', None)
def read_device_collections(self, connection, source_id, dbpath): from sqlite3 import DatabaseError sequence_min = self.get_database_min_id(source_id) sequence_max = sequence_min sequence_dirty = 0 debug_print("Collection Sequence Min: %d, Source Id: %d"%(sequence_min,source_id)) try: cursor = connection.cursor() # Get existing collections query = 'SELECT _id, title FROM collection' cursor.execute(query) except DatabaseError: import traceback tb = traceback.format_exc() raise DeviceError((('The SONY database is corrupted. ' ' Delete the file %s on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' ' any notes/highlights, etc.')%dbpath)+' Underlying error:' '\n'+tb) db_collections = {} for i, row in enumerate(cursor): db_collections[row[1]] = row[0] if row[0] < sequence_min: sequence_dirty = 1 else: sequence_max = max(sequence_max, row[0]) # If the database is 'dirty', then we should fix up the Ids and the sequence number if sequence_dirty == 1: debug_print("Collection Sequence Dirty for Source Id: %d"%source_id) sequence_max = sequence_max + 1 for collection, collectionId in db_collections.items(): if collectionId < sequence_min: # Record the new Id and write it to the DB db_collections[collection] = sequence_max sequence_max = sequence_max + 1 # Fix the collection DB query = 'UPDATE collection SET _id = ? WHERE title = ?' t = (db_collections[collection], collection, ) cursor.execute(query, t) # Fix any references in existing collections query = 'UPDATE collections SET collection_id = ? WHERE collection_id = ?' t = (db_collections[collection], collectionId,) cursor.execute(query, t) self.set_database_sequence_id(connection, 'collection', sequence_max) debug_print("Collection Sequence Max: %d, Source Id: %d"%(sequence_max,source_id)) # Fix up the collections table now... sequence_dirty = 0 sequence_max = sequence_min debug_print("Collections Sequence Min: %d, Source Id: %d"%(sequence_min,source_id)) query = 'SELECT _id FROM collections' cursor.execute(query) db_collection_pairs = [] for i, row in enumerate(cursor): db_collection_pairs.append(row[0]) if row[0] < sequence_min: sequence_dirty = 1 else: sequence_max = max(sequence_max, row[0]) if sequence_dirty == 1: debug_print("Collections Sequence Dirty for Source Id: %d"%source_id) sequence_max = sequence_max + 1 for pairId in db_collection_pairs: if pairId < sequence_min: # Record the new Id and write it to the DB query = 'UPDATE collections SET _id = ? WHERE _id = ?' t = (sequence_max, pairId,) cursor.execute(query, t) sequence_max = sequence_max + 1 self.set_database_sequence_id(connection, 'collections', sequence_max) debug_print("Collections Sequence Max: %d, Source Id: %d"%(sequence_max,source_id)) cursor.close() return db_collections
def read_device_books(self, connection, source_id, dbpath): from sqlite3 import DatabaseError sequence_min = self.get_database_min_id(source_id) sequence_max = sequence_min sequence_dirty = 0 debug_print("Book Sequence Min: %d, Source Id: %d"%(sequence_min,source_id)) try: cursor = connection.cursor() # Get existing books query = 'SELECT file_path, _id FROM books' cursor.execute(query) except DatabaseError: import traceback tb = traceback.format_exc() raise DeviceError((('The SONY database is corrupted. ' ' Delete the file %s on your reader and then disconnect ' ' reconnect it. If you are using an SD card, you ' ' should delete the file on the card as well. Note that ' ' deleting this file will cause your reader to forget ' ' any notes/highlights, etc.')%dbpath)+' Underlying error:' '\n'+tb) # Get the books themselves, but keep track of any that are less than the minimum. # Record what the max id being used is as well. db_books = {} for i, row in enumerate(cursor): if not hasattr(row[0], 'replace'): continue lpath = row[0].replace('\\', '/') db_books[lpath] = row[1] if row[1] < sequence_min: sequence_dirty = 1 else: sequence_max = max(sequence_max, row[1]) # If the database is 'dirty', then we should fix up the Ids and the sequence number if sequence_dirty == 1: debug_print("Book Sequence Dirty for Source Id: %d"%source_id) sequence_max = sequence_max + 1 for book, bookId in db_books.items(): if bookId < sequence_min: # Record the new Id and write it to the DB db_books[book] = sequence_max sequence_max = sequence_max + 1 # Fix the Books DB query = 'UPDATE books SET _id = ? WHERE file_path = ?' t = (db_books[book], book,) cursor.execute(query, t) # Fix any references so that they point back to the right book t = (db_books[book], bookId,) query = 'UPDATE collections SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE annotation SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE bookmark SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE current_position SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE deleted_markups SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE dic_histories SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE freehand SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE history SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE layout_cache SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) query = 'UPDATE preference SET content_id = ? WHERE content_id = ?' cursor.execute(query, t) self.set_database_sequence_id(connection, 'books', sequence_max) debug_print("Book Sequence Max: %d, Source Id: %d"%(sequence_max,source_id)) cursor.close() return db_books
self._card_a_prefix = mp self._card_a_vol = vol['vol'] if verbose: print "FBSD: card a = ", self._card_a_prefix if mtd == 2: self._card_b_prefix = mp self._card_b_vol = vol['vol'] if verbose: print "FBSD: card b = ", self._card_b_prefix # Note that mtd is used as a bool... not incrementing is fine. break mtd += 1 if mtd > 0: return True raise DeviceError(_('Unable to mount the device')) # # ------------------------------------------------------ # # this one is pretty simple: # just umount each of the previously # mounted filesystems, using the stored volume object # def eject_freebsd(self): import dbus # There should be some way to access the -v arg... verbose = False if self._main_prefix: