From dd61e8eb879efa6039fe0749d458d67a433bc61a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 28 Feb 2011 13:59:35 +0000 Subject: [PATCH 1/4] Implement driveinfo for devices. --- src/calibre/devices/apple/driver.py | 2 +- src/calibre/devices/bambook/driver.py | 2 +- src/calibre/devices/folder_device/driver.py | 4 +- src/calibre/devices/hanlin/driver.py | 1 + src/calibre/devices/interface.py | 2 +- src/calibre/devices/prs500/driver.py | 2 +- src/calibre/devices/prs505/driver.py | 3 -- src/calibre/devices/usbms/device.py | 3 +- src/calibre/devices/usbms/driver.py | 44 +++++++++++++++++++-- src/calibre/gui2/device.py | 17 +++++++- src/calibre/gui2/ui.py | 2 + 11 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index aaa9382612..5ead675aab 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -701,7 +701,7 @@ class ITUNES(DriverBase): self.log.info("ITUNES.get_file(): exporting '%s'" % path) outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) - def open(self): + def open(self, library_uuid): ''' Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device. diff --git a/src/calibre/devices/bambook/driver.py b/src/calibre/devices/bambook/driver.py index 3cc0245cf7..f251310d77 100644 --- a/src/calibre/devices/bambook/driver.py +++ b/src/calibre/devices/bambook/driver.py @@ -61,7 +61,7 @@ class BAMBOOK(DeviceConfig, DevicePlugin): detected_device=None) : self.open() - def open(self): + def open(self, library_uuid): # Make sure the Bambook library is ready if not is_bambook_lib_ready(): raise OpenFeedback(_("Unable to connect to Bambook, you need to install Bambook library first.")) diff --git a/src/calibre/devices/folder_device/driver.py b/src/calibre/devices/folder_device/driver.py index d75697a6cb..c08448051d 100644 --- a/src/calibre/devices/folder_device/driver.py +++ b/src/calibre/devices/folder_device/driver.py @@ -47,6 +47,7 @@ class FOLDER_DEVICE(USBMS): #: Icon for this device icon = I('devices/folder.png') METADATA_CACHE = '.metadata.calibre' + DRIVEINFO = '.driveinfo.calibre' _main_prefix = '' _card_a_prefix = None @@ -77,7 +78,8 @@ class FOLDER_DEVICE(USBMS): only_presence=False): return self.is_connected, self - def open(self): + def open(self, library_uuid): + self.current_library_uuid = library_uuid if not self._main_prefix: return False return True diff --git a/src/calibre/devices/hanlin/driver.py b/src/calibre/devices/hanlin/driver.py index 37f8430a66..ba0cca954d 100644 --- a/src/calibre/devices/hanlin/driver.py +++ b/src/calibre/devices/hanlin/driver.py @@ -116,6 +116,7 @@ class BOOX(HANLINV3): author = 'Jesus Manuel Marinho Valcarce' supported_platforms = ['windows', 'osx', 'linux'] METADATA_CACHE = '.metadata.calibre' + DRIVEINFO = '.driveinfo.calibre' # Ordered list of supported formats FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi', diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index bc442f5853..90da55a9db 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -215,7 +215,7 @@ class DevicePlugin(Plugin): return True - def open(self): + def open(self, library_uuid): ''' Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device. diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 445ddd757b..65ecc98a81 100644 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -240,7 +240,7 @@ class PRS500(DeviceConfig, DevicePlugin): def set_progress_reporter(self, report_progress): self.report_progress = report_progress - def open(self) : + def open(self, library_uuid) : """ Claim an interface on the device for communication. Requires write privileges to the device file. diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 3768b8be62..9f17ea22a4 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -153,9 +153,6 @@ class PRS505(USBMS): # updated on every connect self.WANTS_UPDATED_THUMBNAILS = self.settings().extra_customization[2] - def get_device_information(self, end_session=True): - return (self.gui_name, '', '', '') - def filename_callback(self, fname, mi): if getattr(mi, 'application_id', None) is not None: base = fname.rpartition('.')[0] diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index b0857de909..37b2b061e5 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -700,7 +700,7 @@ class Device(DeviceConfig, DevicePlugin): - def open(self): + def open(self, library_uuid): time.sleep(5) self._main_prefix = self._card_a_prefix = self._card_b_prefix = None if islinux: @@ -722,6 +722,7 @@ class Device(DeviceConfig, DevicePlugin): time.sleep(7) self.open_osx() + self.current_library_uuid = library_uuid self.post_open_callback() def post_open_callback(self): diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index ef654ac428..37785612b5 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -10,17 +10,18 @@ driver. It is intended to be subclassed with the relevant parts implemented for a particular device. ''' -import os -import re -import time +import os, re, time, json, uuid from itertools import cycle +from calibre.constants import numeric_version from calibre import prints, isbytestring from calibre.constants import filesystem_encoding, DEBUG from calibre.devices.usbms.cli import CLI from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book from calibre.ebooks.metadata.book.json_codec import JsonCodec +from calibre.utils.config import from_json, to_json +from calibre.utils.date import now BASE_TIME = None def debug_print(*args): @@ -52,10 +53,45 @@ class USBMS(CLI, Device): FORMATS = [] CAN_SET_METADATA = [] METADATA_CACHE = 'metadata.calibre' + DRIVEINFO = 'driveinfo.calibre' + + def _update_driveinfo_record(self, dinfo, prefix): + if not isinstance(dinfo, dict): + dinfo = {} + if dinfo.get('device_store_uuid', None) is None: + dinfo['device_store_uuid'] = unicode(uuid.uuid4()) + dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None) + dinfo['calibre_version'] = '.'.join([unicode(i) for i in numeric_version]) + dinfo['date_last_connected'] = unicode(now()) + dinfo['prefix'] = prefix.replace('\\', '/') + return dinfo + + def _update_driveinfo_file(self, prefix): + if os.path.exists(os.path.join(prefix, self.DRIVEINFO)): + with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: + try: + driveinfo = json.loads(f.read(), object_hook=from_json) + except: + driveinfo = None + driveinfo = self._update_driveinfo_record(driveinfo, prefix) + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + f.write(json.dumps(driveinfo, default=to_json)) + else: + driveinfo = self._update_driveinfo_record({}, prefix) + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + f.write(json.dumps(driveinfo, default=to_json)) + return driveinfo def get_device_information(self, end_session=True): self.report_progress(1.0, _('Get device information...')) - return (self.get_gui_name(), '', '', '') + self.driveinfo = {} + if self._main_prefix is not None: + self.driveinfo['main'] = self._update_driveinfo_file(self._main_prefix) + if self._card_a_prefix is not None: + self.driveinfo['A'] = self._update_driveinfo_file(self._card_a_prefix) + if self._card_b_prefix is not None: + self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix) + return (self.get_gui_name(), '', '', '', self.driveinfo) def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index e4096f5761..972e02a6ab 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -140,6 +140,8 @@ class DeviceManager(Thread): # {{{ self.mount_connection_requests = Queue.Queue(0) self.open_feedback_slot = open_feedback_slot self.open_feedback_msg = open_feedback_msg + self._device_information = None + self.current_library_uuid = None def report_progress(self, *args): pass @@ -159,7 +161,7 @@ class DeviceManager(Thread): # {{{ try: dev.reset(detected_device=detected_device, report_progress=self.report_progress) - dev.open() + dev.open(self.current_library_uuid) except OpenFeedback, e: if dev not in self.ejected_devices: self.open_feedback_msg(dev.get_gui_name(), e.feedback_msg) @@ -194,6 +196,7 @@ class DeviceManager(Thread): # {{{ else: self.connected_slot(False, self.connected_device_kind) self.connected_device = None + self._device_information = None def detect_device(self): self.scanner.scan() @@ -292,9 +295,13 @@ class DeviceManager(Thread): # {{{ def _get_device_information(self): info = self.device.get_device_information(end_session=False) - info = [i.replace('\x00', '').replace('\x01', '') for i in info] + if len(info) < 5: + list(info).append({}) + info = [i.replace('\x00', '').replace('\x01', '') if isinstance(i, basestring) else i + for i in info] cp = self.device.card_prefix(end_session=False) fs = self.device.free_space() + self._device_information = {'info': info, 'prefixes': cp, 'freespace': fs} return info, cp, fs def get_device_information(self, done): @@ -302,6 +309,9 @@ class DeviceManager(Thread): # {{{ return self.create_job(self._get_device_information, done, description=_('Get device information')) + def get_current_device_information(self): + return self._device_information + def _books(self): '''Get metadata from device''' mainlist = self.device.books(oncard=None, end_session=False) @@ -417,6 +427,9 @@ class DeviceManager(Thread): # {{{ return self.create_job(self._view_book, done, args=[path, target], description=_('View book on device')) + def set_current_library_uuid(self, uuid): + self.current_library_uuid = uuid + # }}} class DeviceAction(QAction): # {{{ diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 8844446de6..bf672d43ca 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -296,6 +296,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ traceback.print_exc() if ac.plugin_path is None: raise + self.device_manager.set_current_library_uuid('THIS IS A UUID') if show_gui and self.gui_debug is not None: info_dialog(self, _('Debug mode'), '

' + @@ -461,6 +462,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.memory_view.reset() self.card_a_view.reset() self.card_b_view.reset() + self.device_manager.set_current_library_uuid('THIS IS A UUID') def set_window_title(self): From 160e57ed542cef705e90164f51a331dcebc4847b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 28 Feb 2011 16:20:42 +0000 Subject: [PATCH 2/4] Add a device name to the driveinfo structure, along with the API to set it. --- src/calibre/devices/interface.py | 9 +++++++++ src/calibre/devices/usbms/driver.py | 20 ++++++++++++++++---- src/calibre/gui2/device.py | 4 ++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 90da55a9db..86d1664271 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -447,6 +447,15 @@ class DevicePlugin(Plugin): ''' pass + def set_driveinfo_name(self, location_code, name): + ''' + Set the device name in the driveinfo file to 'name'. This setting will + persist until the file is re-created or the name is changed again. + + Non-disk devices will ignore this request. + ''' + pass + class BookList(list): ''' A list of books. Each Book object must have the fields diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 37785612b5..ef935e239a 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -55,29 +55,33 @@ class USBMS(CLI, Device): METADATA_CACHE = 'metadata.calibre' DRIVEINFO = 'driveinfo.calibre' - def _update_driveinfo_record(self, dinfo, prefix): + def _update_driveinfo_record(self, dinfo, prefix, name=None): if not isinstance(dinfo, dict): dinfo = {} if dinfo.get('device_store_uuid', None) is None: dinfo['device_store_uuid'] = unicode(uuid.uuid4()) + if dinfo.get('device_name') is None: + dinfo['device_name'] = self.get_gui_name() + if name is not None: + dinfo['device_name'] = name dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None) dinfo['calibre_version'] = '.'.join([unicode(i) for i in numeric_version]) dinfo['date_last_connected'] = unicode(now()) dinfo['prefix'] = prefix.replace('\\', '/') return dinfo - def _update_driveinfo_file(self, prefix): + def _update_driveinfo_file(self, prefix, name=None): if os.path.exists(os.path.join(prefix, self.DRIVEINFO)): with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: try: driveinfo = json.loads(f.read(), object_hook=from_json) except: driveinfo = None - driveinfo = self._update_driveinfo_record(driveinfo, prefix) + driveinfo = self._update_driveinfo_record(driveinfo, prefix, name) with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(json.dumps(driveinfo, default=to_json)) else: - driveinfo = self._update_driveinfo_record({}, prefix) + driveinfo = self._update_driveinfo_record({}, prefix, name) with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(json.dumps(driveinfo, default=to_json)) return driveinfo @@ -93,6 +97,14 @@ class USBMS(CLI, Device): self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix) return (self.get_gui_name(), '', '', '', self.driveinfo) + def set_driveinfo_name(self, location_code, name): + if location_code == 'main': + self._update_driveinfo_file(self._main_prefix, name) + elif location_code == 'A': + self._update_driveinfo_file(self._card_a_prefix, name) + elif location_code == 'B': + self._update_driveinfo_file(self._card_b_prefix, name) + def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 972e02a6ab..955e287522 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -430,6 +430,10 @@ class DeviceManager(Thread): # {{{ def set_current_library_uuid(self, uuid): self.current_library_uuid = uuid + def set_driveinfo_name(self, location_code, name): + if self.connected_device: + self.connected_device.set_driveinfo_name(location_code, name) + # }}} class DeviceAction(QAction): # {{{ From 5d317eb53456b165b7dd0c755a6919b8e205e4bb Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 28 Feb 2011 16:30:27 +0000 Subject: [PATCH 3/4] Add the location code to the automatically generated device name --- src/calibre/devices/usbms/driver.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index ef935e239a..392c4c2305 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -55,13 +55,13 @@ class USBMS(CLI, Device): METADATA_CACHE = 'metadata.calibre' DRIVEINFO = 'driveinfo.calibre' - def _update_driveinfo_record(self, dinfo, prefix, name=None): + def _update_driveinfo_record(self, dinfo, prefix, location_code, name=None): if not isinstance(dinfo, dict): dinfo = {} if dinfo.get('device_store_uuid', None) is None: dinfo['device_store_uuid'] = unicode(uuid.uuid4()) if dinfo.get('device_name') is None: - dinfo['device_name'] = self.get_gui_name() + dinfo['device_name'] = self.get_gui_name() + '_' + location_code if name is not None: dinfo['device_name'] = name dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None) @@ -70,18 +70,19 @@ class USBMS(CLI, Device): dinfo['prefix'] = prefix.replace('\\', '/') return dinfo - def _update_driveinfo_file(self, prefix, name=None): + def _update_driveinfo_file(self, prefix, location_code, name=None): if os.path.exists(os.path.join(prefix, self.DRIVEINFO)): with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: try: driveinfo = json.loads(f.read(), object_hook=from_json) except: driveinfo = None - driveinfo = self._update_driveinfo_record(driveinfo, prefix, name) + driveinfo = self._update_driveinfo_record(driveinfo, prefix, + location_code, name) with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(json.dumps(driveinfo, default=to_json)) else: - driveinfo = self._update_driveinfo_record({}, prefix, name) + driveinfo = self._update_driveinfo_record({}, prefix, location_code, name) with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(json.dumps(driveinfo, default=to_json)) return driveinfo @@ -90,20 +91,20 @@ class USBMS(CLI, Device): self.report_progress(1.0, _('Get device information...')) self.driveinfo = {} if self._main_prefix is not None: - self.driveinfo['main'] = self._update_driveinfo_file(self._main_prefix) + self.driveinfo['main'] = self._update_driveinfo_file(self._main_prefix, 'main') if self._card_a_prefix is not None: - self.driveinfo['A'] = self._update_driveinfo_file(self._card_a_prefix) + self.driveinfo['A'] = self._update_driveinfo_file(self._card_a_prefix, 'A') if self._card_b_prefix is not None: - self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix) + self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix, 'B') return (self.get_gui_name(), '', '', '', self.driveinfo) def set_driveinfo_name(self, location_code, name): if location_code == 'main': - self._update_driveinfo_file(self._main_prefix, name) + self._update_driveinfo_file(self._main_prefix, location_code, name) elif location_code == 'A': - self._update_driveinfo_file(self._card_a_prefix, name) + self._update_driveinfo_file(self._card_a_prefix, location_code, name) elif location_code == 'B': - self._update_driveinfo_file(self._card_b_prefix, name) + self._update_driveinfo_file(self._card_b_prefix, location_code, name) def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext From be4984f4928a00bab9bf34cffb85fe2d808a7aab Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 28 Feb 2011 16:48:10 +0000 Subject: [PATCH 4/4] Change server to use a getter for isbn instead of data[FIELD_MAP] --- src/calibre/library/server/xml.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py index efbceb9771..c6d6f9db8f 100644 --- a/src/calibre/library/server/xml.py +++ b/src/calibre/library/server/xml.py @@ -89,13 +89,16 @@ class XMLServer(object): for x in ('id', 'title', 'sort', 'author_sort', 'rating', 'size'): kwargs[x] = serialize(record[FM[x]]) - for x in ('isbn', 'formats', 'series', 'tags', 'publisher', + for x in ('formats', 'series', 'tags', 'publisher', 'comments'): y = record[FM[x]] if x == 'tags': y = format_tag_string(y, ',', ignore_max=True) kwargs[x] = serialize(y) if y else '' + isbn = self.db.isbn(record[FM['id']], index_is_id=True) + kwargs['isbn'] = serialize(isbn if isbn else '') + kwargs['safe_title'] = ascii_filename(kwargs['title']) c = kwargs.pop('comments')