Add an interface to permit starting and stopping of devices without disabling them. Will be used by the smartdevice driver

This commit is contained in:
Charles Haley 2012-07-24 14:18:52 +02:00
parent 61ddf184f0
commit 76c0892b51
5 changed files with 160 additions and 7 deletions

View File

@ -514,6 +514,54 @@ class DevicePlugin(Plugin):
''' '''
pass pass
# Dynamic control interface
def is_dynamically_controllable(self):
'''
Called by the device manager when starting plugins. If this method returns
a string, then a) it supports the device manager's dynamic control
interface, and b) that name is to be used when talking to the plugin
'''
return None
def start_plugin(self):
'''
This method is called to start the plugin. The plugin should begin
to accept device connections however it does that. If the plugin is
already accepting connections, then do nothing.
'''
pass
def stop_plugin(self):
'''
This method is called to stop the plugin. The plugin should no longer
accept connections, and should cleanup behind itself. It is likely that
this method should call shutdown. If the plugin is already not accepting
connections, then do nothing.
'''
pass
def get_option(self, opt_string):
'''
Return the value of the option indicated by opt_string. This method can
be called when the plugin is not started. Return None if the option does
not exist.
'''
return None
def set_option(self, opt_string, opt_value):
'''
Set the value of the option indicated by opt_string. This method can
be called when the plugin is not started.
'''
pass
def is_running(self):
'''
Return True if the plugin is started, otherwise false
'''
return False
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

View File

@ -24,6 +24,7 @@ class ShareConnMenu(QMenu): # {{{
config_email = pyqtSignal() config_email = pyqtSignal()
toggle_server = pyqtSignal() toggle_server = pyqtSignal()
toggle_smartdevice = pyqtSignal()
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
def __init__(self, parent=None): def __init__(self, parent=None):
@ -56,6 +57,11 @@ class ShareConnMenu(QMenu): # {{{
_('Start Content Server')) _('Start Content Server'))
self.toggle_server_action.triggered.connect(lambda x: self.toggle_server_action.triggered.connect(lambda x:
self.toggle_server.emit()) self.toggle_server.emit())
self.toggle_smartdevice_action = \
self.addAction(QIcon(I('devices/galaxy_s3.png')),
_('Start Smart Device Connections'))
self.toggle_smartdevice_action.triggered.connect(lambda x:
self.toggle_smartdevice.emit())
self.addSeparator() self.addSeparator()
self.email_actions = [] self.email_actions = []
@ -80,6 +86,15 @@ class ShareConnMenu(QMenu): # {{{
text = _('Stop Content Server') + ' [%s]'%get_external_ip() text = _('Stop Content Server') + ' [%s]'%get_external_ip()
self.toggle_server_action.setText(text) self.toggle_server_action.setText(text)
def smartdevice_state_changed(self, accepting):
if accepting:
self.toggle_smartdevice_action.setText(_('Stop Smart Device Connections'))
else:
self.toggle_smartdevice_action.setText(_('Start Smart Device Connections'))
def hide_smartdevice_menus(self):
self.toggle_smartdevice_action.setVisible(False)
def build_email_entries(self, sync_menu): def build_email_entries(self, sync_menu):
from calibre.gui2.device import DeviceAction from calibre.gui2.device import DeviceAction
for ac in self.email_actions: for ac in self.email_actions:
@ -158,6 +173,7 @@ class ConnectShareAction(InterfaceAction):
def genesis(self): def genesis(self):
self.share_conn_menu = ShareConnMenu(self.gui) self.share_conn_menu = ShareConnMenu(self.gui)
self.share_conn_menu.toggle_server.connect(self.toggle_content_server) self.share_conn_menu.toggle_server.connect(self.toggle_content_server)
self.share_conn_menu.toggle_smartdevice.connect(self.toggle_smartdevice)
self.share_conn_menu.config_email.connect(partial( self.share_conn_menu.config_email.connect(partial(
self.gui.iactions['Preferences'].do_config, self.gui.iactions['Preferences'].do_config,
initial_plugin=('Sharing', 'Email'))) initial_plugin=('Sharing', 'Email')))
@ -200,8 +216,23 @@ class ConnectShareAction(InterfaceAction):
if not self.stopping_msg.isVisible(): if not self.stopping_msg.isVisible():
self.stopping_msg.exec_() self.stopping_msg.exec_()
return return
self.gui.content_server = None self.gui.content_server = None
self.stopping_msg.accept() self.stopping_msg.accept()
def toggle_smartdevice(self):
info_dialog(self.gui, _('Foobar'),
_('Start server bla bla blah...'),
show_copy_button=False, show=True)
if self.gui.device_manager.is_running('smartdevice'):
self.gui.device_manager.stop_plugin('smartdevice')
else:
self.gui.device_manager.start_plugin('smartdevice')
self.share_conn_menu.smartdevice_state_changed(
self.gui.device_manager.is_running('smartdevice'))
def smartdevice_state_changed(self, running):
self.share_conn_menu.smartdevice_state_changed(running)
def check_smartdevice_menus(self):
if not self.gui.device_manager.is_enabled('smartdevice'):
self.share_conn_menu.hide_smartdevice_menus()

View File

@ -145,6 +145,8 @@ class DeviceManager(Thread): # {{{
self._device_information = None self._device_information = None
self.current_library_uuid = None self.current_library_uuid = None
self.call_shutdown_on_disconnect = False self.call_shutdown_on_disconnect = False
self.devices_initialized = Queue.Queue(0)
self.dynamic_plugins = {}
def report_progress(self, *args): def report_progress(self, *args):
pass pass
@ -286,6 +288,10 @@ class DeviceManager(Thread): # {{{
# Do any device-specific startup processing. # Do any device-specific startup processing.
for d in self.devices: for d in self.devices:
self.run_startup(d) self.run_startup(d)
n = d.is_dynamically_controllable()
if n:
self.dynamic_plugins[n] = d
self.devices_initialized.put(None)
while self.keep_going: while self.keep_going:
kls = None kls = None
@ -508,6 +514,59 @@ class DeviceManager(Thread): # {{{
if self.connected_device: if self.connected_device:
self.connected_device.set_driveinfo_name(location_code, name) self.connected_device.set_driveinfo_name(location_code, name)
# dynamic plugin interface
def start_plugin(self, name):
try:
d = self.dynamic_plugins.get(name, None)
if d:
d.start_plugin()
except:
pass
def stop_plugin(self, name):
try:
d = self.dynamic_plugins.get(name, None)
if d:
d.stop_plugin()
except:
pass
def get_option(self, name, opt_string):
try:
d = self.dynamic_plugins.get(name, None)
if d:
return d.get_option(opt_string)
except:
pass
return None
def set_option(self, name, opt_string, opt_value):
try:
d = self.dynamic_plugins.get(name, None)
if d:
d.set_option(opt_string, opt_value)
except:
pass
def is_running(self, name):
try:
d = self.dynamic_plugins.get(name, None)
if d:
return d.is_running()
except:
pass
return False
def is_enabled(self, name):
try:
d = self.dynamic_plugins.get(name, None)
if d:
return True
except:
pass
return False
# }}} # }}}
class DeviceAction(QAction): # {{{ class DeviceAction(QAction): # {{{
@ -708,6 +767,7 @@ class DeviceMixin(object): # {{{
self.job_manager, Dispatcher(self.status_bar.show_message), self.job_manager, Dispatcher(self.status_bar.show_message),
Dispatcher(self.show_open_feedback)) Dispatcher(self.show_open_feedback))
self.device_manager.start() self.device_manager.start()
self.device_manager.devices_initialized.get()
if tweaks['auto_connect_to_folder']: if tweaks['auto_connect_to_folder']:
self.connect_to_folder_named(tweaks['auto_connect_to_folder']) self.connect_to_folder_named(tweaks['auto_connect_to_folder'])

View File

@ -337,6 +337,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if config['autolaunch_server']: if config['autolaunch_server']:
self.start_content_server() self.start_content_server()
smartdevice_actions = self.iactions['Connect Share']
smartdevice_actions.check_smartdevice_menus()
if self.device_manager.get_option('smartdevice', 'autostart'):
try:
self.device_manager.start_plugin('smartdevice')
smartdevice_actions.smartdevice_state_changed(True)
except:
pass
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection) self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
self.read_settings() self.read_settings()

View File

@ -955,11 +955,16 @@ class Reaper(threading.Thread):
return return
if globals()['_GLOBAL_DONE']: if globals()['_GLOBAL_DONE']:
return return
try:
# can get here in a race condition with shutdown. Swallow the
# exception and run around the loop again.
now = currentTimeMillis() now = currentTimeMillis()
for record in self.zeroconf.cache.entries(): for record in self.zeroconf.cache.entries():
if record.isExpired(now): if record.isExpired(now):
self.zeroconf.updateRecord(now, record) self.zeroconf.updateRecord(now, record)
self.zeroconf.cache.remove(record) self.zeroconf.cache.remove(record)
except:
pass
class ServiceBrowser(threading.Thread): class ServiceBrowser(threading.Thread):