Device susbsytem: Create a drive info file named driveinfo.calibre in the root of each device drive for USB connected devices. This file contains various useful data. API Change: The open method of the device plugins now accepts an extra parameter library_uuid which is the id of the calibre library connected tot eh device

This commit is contained in:
Kovid Goyal 2011-02-28 19:57:19 -07:00
commit 6335d9be20
12 changed files with 101 additions and 17 deletions

View File

@ -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.

View File

@ -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."))

View File

@ -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

View File

@ -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',

View File

@ -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.
@ -260,6 +260,8 @@ class DevicePlugin(Plugin):
Ask device for device information. See L{DeviceInfoQuery}.
:return: (device name, device version, software version on device, mime type)
The tuple can optionally have a fifth element, which is a
drive information diction. See usbms.driver for an example.
"""
raise NotImplementedError()
@ -447,6 +449,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

View File

@ -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.

View File

@ -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]

View File

@ -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):

View File

@ -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, isoformat
BASE_TIME = None
def debug_print(*args):
@ -52,10 +53,59 @@ class USBMS(CLI, Device):
FORMATS = []
CAN_SET_METADATA = []
METADATA_CACHE = 'metadata.calibre'
DRIVEINFO = 'driveinfo.calibre'
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()
if name is not None:
dinfo['device_name'] = name
dinfo['location_code'] = location_code
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'] = isoformat(now())
dinfo['prefix'] = prefix.replace('\\', '/')
return dinfo
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,
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, location_code, name)
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, 'main')
if self._card_a_prefix is not None:
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, '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, location_code, name)
elif location_code == 'A':
self._update_driveinfo_file(self._card_a_prefix, location_code, name)
elif location_code == 'B':
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

View File

@ -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:
info = tuple(list(info) + [{}])
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,13 @@ 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
def set_driveinfo_name(self, location_code, name):
if self.connected_device:
self.connected_device.set_driveinfo_name(location_code, name)
# }}}
class DeviceAction(QAction): # {{{

View File

@ -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(db.library_id)
if show_gui and self.gui_debug is not None:
info_dialog(self, _('Debug mode'), '<p>' +
@ -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(db.library_id)
def set_window_title(self):

View File

@ -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',
'comments'):
for x in ('formats', 'series', 'tags', 'publisher',
'comments', 'identifiers'):
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')