Changes required for smart device driver

This commit is contained in:
Kovid Goyal 2012-07-23 23:55:19 +05:30
commit 07ce6e0a4f
8 changed files with 133 additions and 32 deletions

View File

@ -15,6 +15,8 @@ class DevicePlugin(Plugin):
#: Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"]
# If True, the config dialog will not show the formats box
HIDE_FORMATS_CONFIG_BOX = False
#: VENDOR_ID can be either an integer, a list of integers or a dictionary
#: If it is a dictionary, it must be a dictionary of dictionaries,
@ -496,6 +498,22 @@ class DevicePlugin(Plugin):
'''
return paths
def startup(self):
'''
Called when calibre is is starting the device. Do any initialization
required. Note that multiple instances of the class can be instantiated,
and thus __init__ can be called multiple times, but only one instance
will have this method called.
'''
pass
def shutdown(self):
'''
Called when calibre is shutting down, either for good or in preparation
to restart. Do any cleanup required.
'''
pass
class BookList(list):
'''
A list of books. Each Book object must have the fields

View File

@ -117,8 +117,8 @@ class JsonCodec(object):
def __init__(self):
self.field_metadata = FieldMetadata()
def encode_to_file(self, file, booklist):
file.write(json.dumps(self.encode_booklist_metadata(booklist),
def encode_to_file(self, file_, booklist):
file_.write(json.dumps(self.encode_booklist_metadata(booklist),
indent=2, encoding='utf-8'))
def encode_booklist_metadata(self, booklist):
@ -156,21 +156,28 @@ class JsonCodec(object):
else:
return object_to_unicode(value)
def decode_from_file(self, file, booklist, book_class, prefix):
def decode_from_file(self, file_, booklist, book_class, prefix):
js = []
try:
js = json.load(file, encoding='utf-8')
js = json.load(file_, encoding='utf-8')
for item in js:
book = book_class(prefix, item.get('lpath', None))
for key in item.keys():
meta = self.decode_metadata(key, item[key])
booklist.append(self.raw_to_book(item, book_class, prefix))
except:
print 'exception during JSON decode_from_file'
traceback.print_exc()
def raw_to_book(self, json_book, book_class, prefix):
try:
book = book_class(prefix, json_book.get('lpath', None))
for key,val in json_book.iteritems():
meta = self.decode_metadata(key, val)
if key == 'user_metadata':
book.set_all_user_metadata(meta)
else:
if key == 'classifiers':
key = 'identifiers'
setattr(book, key, meta)
booklist.append(book)
return book
except:
print 'exception during JSON decoding'
traceback.print_exc()

View File

@ -144,6 +144,7 @@ class DeviceManager(Thread): # {{{
self.open_feedback_msg = open_feedback_msg
self._device_information = None
self.current_library_uuid = None
self.call_shutdown_on_disconnect = False
def report_progress(self, *args):
pass
@ -197,6 +198,13 @@ class DeviceManager(Thread): # {{{
self.ejected_devices.remove(self.connected_device)
else:
self.connected_slot(False, self.connected_device_kind)
if self.call_shutdown_on_disconnect:
# The current device is an instance of a plugin class instantiated
# to handle this connection, probably as a mounted device. We are
# now abandoning the instance that we created, so we tell it that it
# is being shut down.
self.connected_device.shutdown()
self.call_shutdown_on_disconnect = False
self.connected_device = None
self._device_information = None
@ -265,7 +273,20 @@ class DeviceManager(Thread): # {{{
except Queue.Empty:
pass
def run_startup(self, dev):
name = 'unknown'
try:
name = dev.__class__.__name__
dev.startup()
except:
prints('Startup method for device %s threw exception'%name)
traceback.print_exc()
def run(self):
# Do any device-specific startup processing.
for d in self.devices:
self.run_startup(d)
while self.keep_going:
kls = None
while True:
@ -277,6 +298,11 @@ class DeviceManager(Thread): # {{{
if kls is not None:
try:
dev = kls(folder_path)
# We just created a new device instance. Call its startup
# method and set the flag to call the shutdown method when
# it disconnects.
self.run_startup(dev)
self.call_shutdown_on_disconnect = True
self.do_connect([[dev, None],], device_kind=device_kind)
except:
prints('Unable to open %s as device (%s)'%(device_kind, folder_path))
@ -295,6 +321,13 @@ class DeviceManager(Thread): # {{{
break
time.sleep(self.sleep_time)
# We are exiting. Call the shutdown method for each plugin
for p in self.devices:
try:
p.shutdown()
except:
pass
def create_job_step(self, func, done, description, to_job, args=[], kwargs={}):
job = DeviceJob(func, done, self.job_manager,
args=args, kwargs=kwargs, description=description)

View File

@ -43,6 +43,9 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
self.connect(self.column_up, SIGNAL('clicked()'), self.up_column)
self.connect(self.column_down, SIGNAL('clicked()'), self.down_column)
if device.HIDE_FORMATS_CONFIG_BOX:
self.groupBox.hide()
if supports_subdirs:
self.opt_use_subdirs.setChecked(self.settings.use_subdirs)
else:

View File

@ -103,6 +103,19 @@
<item row="6" column="0">
<layout class="QGridLayout" name="extra_layout"/>
</item>
<item row="7" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">

View File

@ -17,7 +17,7 @@ from calibre.utils.date import fromtimestamp
from calibre.library.server import listen_on, log_access_file, log_error_file
from calibre.library.server.utils import expose, AuthController
from calibre.utils.mdns import publish as publish_zeroconf, \
stop_server as stop_zeroconf, get_external_ip
unpublish as unpublish_zeroconf, get_external_ip
from calibre.library.server.content import ContentServer
from calibre.library.server.mobile import MobileServer
from calibre.library.server.xml import XMLServer
@ -78,13 +78,18 @@ class BonJour(SimplePlugin): # {{{
SimplePlugin.__init__(self, engine)
self.port = port
self.prefix = prefix
self.mdns_services = [
('Books in calibre', '_stanza._tcp', self.port,
{'path':self.prefix+'/stanza'}),
('Books in calibre', '_calibre._tcp', self.port,
{'path':self.prefix+'/opds'}),
]
def start(self):
try:
publish_zeroconf('Books in calibre', '_stanza._tcp',
self.port, {'path':self.prefix+'/stanza'})
publish_zeroconf('Books in calibre', '_calibre._tcp',
self.port, {'path':self.prefix+'/opds'})
for s in self.mdns_services:
publish_zeroconf(*s)
except:
import traceback
cherrypy.log.error('Failed to start BonJour:')
@ -94,7 +99,8 @@ class BonJour(SimplePlugin): # {{{
def stop(self):
try:
stop_zeroconf()
for s in self.mdns_services:
unpublish_zeroconf(*s)
except:
import traceback
cherrypy.log.error('Failed to stop BonJour:')

View File

@ -871,6 +871,8 @@ class Engine(threading.Thread):
from calibre.constants import DEBUG
try:
rr, wr, er = select.select(rs, [], [], self.timeout)
if globals()['_GLOBAL_DONE']:
continue
for socket in rr:
try:
self.readers[socket].handle_read()
@ -1419,6 +1421,9 @@ class Zeroconf(object):
i += 1
nextTime += _UNREGISTER_TIME
def countRegisteredServices(self):
return len(self.services)
def checkService(self, info):
"""Checks the network for a unique service name, modifying the
ServiceInfo passed in if it is not unique."""

View File

@ -47,18 +47,8 @@ def start_server():
return _server
def publish(desc, type, port, properties=None, add_hostname=True):
'''
Publish a service.
:param desc: Description of service
:param type: Name and type of service. For example _stanza._tcp
:param port: Port the service listens on
:param properties: An optional dictionary whose keys and values will be put
into the TXT record.
'''
def create_service(desc, type, port, properties, add_hostname):
port = int(port)
server = start_server()
try:
hostname = socket.gethostname().partition('.')[0]
except:
@ -69,13 +59,39 @@ def publish(desc, type, port, properties=None, add_hostname=True):
local_ip = get_external_ip()
type = type+'.local.'
from calibre.utils.Zeroconf import ServiceInfo
service = ServiceInfo(type, desc+'.'+type,
return ServiceInfo(type, desc+'.'+type,
address=socket.inet_aton(local_ip),
port=port,
properties=properties,
server=hostname+'.local.')
def publish(desc, type, port, properties=None, add_hostname=True):
'''
Publish a service.
:param desc: Description of service
:param type: Name and type of service. For example _stanza._tcp
:param port: Port the service listens on
:param properties: An optional dictionary whose keys and values will be put
into the TXT record.
'''
server = start_server()
service = create_service(desc, type, port, properties, add_hostname)
server.registerService(service)
def unpublish(desc, type, port, properties=None, add_hostname=True):
'''
Unpublish a service.
The parameters must be the same as used in the corresponding call to publish
'''
server = start_server()
service = create_service(desc, type, port, properties, add_hostname)
server.unregisterService(service)
if server.countRegisteredServices() == 0:
stop_server()
def stop_server():
global _server
if _server is not None: