mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Changes required for smart device driver
This commit is contained in:
commit
07ce6e0a4f
@ -15,6 +15,8 @@ class DevicePlugin(Plugin):
|
|||||||
|
|
||||||
#: Ordered list of supported formats
|
#: Ordered list of supported formats
|
||||||
FORMATS = ["lrf", "rtf", "pdf", "txt"]
|
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
|
#: 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,
|
#: If it is a dictionary, it must be a dictionary of dictionaries,
|
||||||
@ -496,6 +498,22 @@ class DevicePlugin(Plugin):
|
|||||||
'''
|
'''
|
||||||
return paths
|
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):
|
class BookList(list):
|
||||||
'''
|
'''
|
||||||
A list of books. Each Book object must have the fields
|
A list of books. Each Book object must have the fields
|
||||||
|
@ -117,8 +117,8 @@ class JsonCodec(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.field_metadata = FieldMetadata()
|
self.field_metadata = FieldMetadata()
|
||||||
|
|
||||||
def encode_to_file(self, file, booklist):
|
def encode_to_file(self, file_, booklist):
|
||||||
file.write(json.dumps(self.encode_booklist_metadata(booklist),
|
file_.write(json.dumps(self.encode_booklist_metadata(booklist),
|
||||||
indent=2, encoding='utf-8'))
|
indent=2, encoding='utf-8'))
|
||||||
|
|
||||||
def encode_booklist_metadata(self, booklist):
|
def encode_booklist_metadata(self, booklist):
|
||||||
@ -156,21 +156,28 @@ class JsonCodec(object):
|
|||||||
else:
|
else:
|
||||||
return object_to_unicode(value)
|
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 = []
|
js = []
|
||||||
try:
|
try:
|
||||||
js = json.load(file, encoding='utf-8')
|
js = json.load(file_, encoding='utf-8')
|
||||||
for item in js:
|
for item in js:
|
||||||
book = book_class(prefix, item.get('lpath', None))
|
booklist.append(self.raw_to_book(item, book_class, prefix))
|
||||||
for key in item.keys():
|
except:
|
||||||
meta = self.decode_metadata(key, item[key])
|
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':
|
if key == 'user_metadata':
|
||||||
book.set_all_user_metadata(meta)
|
book.set_all_user_metadata(meta)
|
||||||
else:
|
else:
|
||||||
if key == 'classifiers':
|
if key == 'classifiers':
|
||||||
key = 'identifiers'
|
key = 'identifiers'
|
||||||
setattr(book, key, meta)
|
setattr(book, key, meta)
|
||||||
booklist.append(book)
|
return book
|
||||||
except:
|
except:
|
||||||
print 'exception during JSON decoding'
|
print 'exception during JSON decoding'
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
@ -144,6 +144,7 @@ class DeviceManager(Thread): # {{{
|
|||||||
self.open_feedback_msg = open_feedback_msg
|
self.open_feedback_msg = open_feedback_msg
|
||||||
self._device_information = None
|
self._device_information = None
|
||||||
self.current_library_uuid = None
|
self.current_library_uuid = None
|
||||||
|
self.call_shutdown_on_disconnect = False
|
||||||
|
|
||||||
def report_progress(self, *args):
|
def report_progress(self, *args):
|
||||||
pass
|
pass
|
||||||
@ -197,6 +198,13 @@ class DeviceManager(Thread): # {{{
|
|||||||
self.ejected_devices.remove(self.connected_device)
|
self.ejected_devices.remove(self.connected_device)
|
||||||
else:
|
else:
|
||||||
self.connected_slot(False, self.connected_device_kind)
|
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.connected_device = None
|
||||||
self._device_information = None
|
self._device_information = None
|
||||||
|
|
||||||
@ -265,7 +273,20 @@ class DeviceManager(Thread): # {{{
|
|||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
pass
|
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):
|
def run(self):
|
||||||
|
# Do any device-specific startup processing.
|
||||||
|
for d in self.devices:
|
||||||
|
self.run_startup(d)
|
||||||
|
|
||||||
while self.keep_going:
|
while self.keep_going:
|
||||||
kls = None
|
kls = None
|
||||||
while True:
|
while True:
|
||||||
@ -277,6 +298,11 @@ class DeviceManager(Thread): # {{{
|
|||||||
if kls is not None:
|
if kls is not None:
|
||||||
try:
|
try:
|
||||||
dev = kls(folder_path)
|
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)
|
self.do_connect([[dev, None],], device_kind=device_kind)
|
||||||
except:
|
except:
|
||||||
prints('Unable to open %s as device (%s)'%(device_kind, folder_path))
|
prints('Unable to open %s as device (%s)'%(device_kind, folder_path))
|
||||||
@ -295,6 +321,13 @@ class DeviceManager(Thread): # {{{
|
|||||||
break
|
break
|
||||||
time.sleep(self.sleep_time)
|
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={}):
|
def create_job_step(self, func, done, description, to_job, args=[], kwargs={}):
|
||||||
job = DeviceJob(func, done, self.job_manager,
|
job = DeviceJob(func, done, self.job_manager,
|
||||||
args=args, kwargs=kwargs, description=description)
|
args=args, kwargs=kwargs, description=description)
|
||||||
|
@ -43,6 +43,9 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
self.connect(self.column_up, SIGNAL('clicked()'), self.up_column)
|
self.connect(self.column_up, SIGNAL('clicked()'), self.up_column)
|
||||||
self.connect(self.column_down, SIGNAL('clicked()'), self.down_column)
|
self.connect(self.column_down, SIGNAL('clicked()'), self.down_column)
|
||||||
|
|
||||||
|
if device.HIDE_FORMATS_CONFIG_BOX:
|
||||||
|
self.groupBox.hide()
|
||||||
|
|
||||||
if supports_subdirs:
|
if supports_subdirs:
|
||||||
self.opt_use_subdirs.setChecked(self.settings.use_subdirs)
|
self.opt_use_subdirs.setChecked(self.settings.use_subdirs)
|
||||||
else:
|
else:
|
||||||
|
@ -103,6 +103,19 @@
|
|||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
<layout class="QGridLayout" name="extra_layout"/>
|
<layout class="QGridLayout" name="extra_layout"/>
|
||||||
</item>
|
</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">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -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 import listen_on, log_access_file, log_error_file
|
||||||
from calibre.library.server.utils import expose, AuthController
|
from calibre.library.server.utils import expose, AuthController
|
||||||
from calibre.utils.mdns import publish as publish_zeroconf, \
|
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.content import ContentServer
|
||||||
from calibre.library.server.mobile import MobileServer
|
from calibre.library.server.mobile import MobileServer
|
||||||
from calibre.library.server.xml import XMLServer
|
from calibre.library.server.xml import XMLServer
|
||||||
@ -78,13 +78,18 @@ class BonJour(SimplePlugin): # {{{
|
|||||||
SimplePlugin.__init__(self, engine)
|
SimplePlugin.__init__(self, engine)
|
||||||
self.port = port
|
self.port = port
|
||||||
self.prefix = prefix
|
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):
|
def start(self):
|
||||||
try:
|
try:
|
||||||
publish_zeroconf('Books in calibre', '_stanza._tcp',
|
for s in self.mdns_services:
|
||||||
self.port, {'path':self.prefix+'/stanza'})
|
publish_zeroconf(*s)
|
||||||
publish_zeroconf('Books in calibre', '_calibre._tcp',
|
|
||||||
self.port, {'path':self.prefix+'/opds'})
|
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
cherrypy.log.error('Failed to start BonJour:')
|
cherrypy.log.error('Failed to start BonJour:')
|
||||||
@ -94,7 +99,8 @@ class BonJour(SimplePlugin): # {{{
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
try:
|
try:
|
||||||
stop_zeroconf()
|
for s in self.mdns_services:
|
||||||
|
unpublish_zeroconf(*s)
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
cherrypy.log.error('Failed to stop BonJour:')
|
cherrypy.log.error('Failed to stop BonJour:')
|
||||||
|
@ -871,6 +871,8 @@ class Engine(threading.Thread):
|
|||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
try:
|
try:
|
||||||
rr, wr, er = select.select(rs, [], [], self.timeout)
|
rr, wr, er = select.select(rs, [], [], self.timeout)
|
||||||
|
if globals()['_GLOBAL_DONE']:
|
||||||
|
continue
|
||||||
for socket in rr:
|
for socket in rr:
|
||||||
try:
|
try:
|
||||||
self.readers[socket].handle_read()
|
self.readers[socket].handle_read()
|
||||||
@ -1419,6 +1421,9 @@ class Zeroconf(object):
|
|||||||
i += 1
|
i += 1
|
||||||
nextTime += _UNREGISTER_TIME
|
nextTime += _UNREGISTER_TIME
|
||||||
|
|
||||||
|
def countRegisteredServices(self):
|
||||||
|
return len(self.services)
|
||||||
|
|
||||||
def checkService(self, info):
|
def checkService(self, info):
|
||||||
"""Checks the network for a unique service name, modifying the
|
"""Checks the network for a unique service name, modifying the
|
||||||
ServiceInfo passed in if it is not unique."""
|
ServiceInfo passed in if it is not unique."""
|
||||||
|
@ -47,18 +47,8 @@ def start_server():
|
|||||||
|
|
||||||
return _server
|
return _server
|
||||||
|
|
||||||
def publish(desc, type, port, properties=None, add_hostname=True):
|
def create_service(desc, type, port, properties, add_hostname):
|
||||||
'''
|
|
||||||
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.
|
|
||||||
'''
|
|
||||||
port = int(port)
|
port = int(port)
|
||||||
server = start_server()
|
|
||||||
try:
|
try:
|
||||||
hostname = socket.gethostname().partition('.')[0]
|
hostname = socket.gethostname().partition('.')[0]
|
||||||
except:
|
except:
|
||||||
@ -69,13 +59,39 @@ def publish(desc, type, port, properties=None, add_hostname=True):
|
|||||||
local_ip = get_external_ip()
|
local_ip = get_external_ip()
|
||||||
type = type+'.local.'
|
type = type+'.local.'
|
||||||
from calibre.utils.Zeroconf import ServiceInfo
|
from calibre.utils.Zeroconf import ServiceInfo
|
||||||
service = ServiceInfo(type, desc+'.'+type,
|
return ServiceInfo(type, desc+'.'+type,
|
||||||
address=socket.inet_aton(local_ip),
|
address=socket.inet_aton(local_ip),
|
||||||
port=port,
|
port=port,
|
||||||
properties=properties,
|
properties=properties,
|
||||||
server=hostname+'.local.')
|
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)
|
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():
|
def stop_server():
|
||||||
global _server
|
global _server
|
||||||
if _server is not None:
|
if _server is not None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user