diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index 9510dcf3d1..26239b59e7 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -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
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 3b52821c1b..cc9b6f252d 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -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])
- if key == 'user_metadata':
- book.set_all_user_metadata(meta)
- else:
- if key == 'classifiers':
- key = 'identifiers'
- setattr(book, key, meta)
- booklist.append(book)
+ 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)
+ return book
except:
print 'exception during JSON decoding'
traceback.print_exc()
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 1dcadf7b65..17f1e47853 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -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)
diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py
index 94843f90e3..b47a80b6ad 100644
--- a/src/calibre/gui2/device_drivers/configwidget.py
+++ b/src/calibre/gui2/device_drivers/configwidget.py
@@ -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:
diff --git a/src/calibre/gui2/device_drivers/configwidget.ui b/src/calibre/gui2/device_drivers/configwidget.ui
index 92324fd1a7..d8c3c44e22 100644
--- a/src/calibre/gui2/device_drivers/configwidget.ui
+++ b/src/calibre/gui2/device_drivers/configwidget.ui
@@ -103,6 +103,19 @@
-
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
-
diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py
index 0b5fead634..d912165734 100644
--- a/src/calibre/library/server/base.py
+++ b/src/calibre/library/server/base.py
@@ -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:')
diff --git a/src/calibre/utils/Zeroconf.py b/src/calibre/utils/Zeroconf.py
index 0e55e8f516..b722865101 100755
--- a/src/calibre/utils/Zeroconf.py
+++ b/src/calibre/utils/Zeroconf.py
@@ -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."""
diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py
index 2be6bef49b..9232aab994 100644
--- a/src/calibre/utils/mdns.py
+++ b/src/calibre/utils/mdns.py
@@ -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: