Improved thread handling in device_manager dynamic plugin methods. Improved smartdevice dialog box.

This commit is contained in:
Charles Haley 2012-07-24 19:23:45 +02:00
parent 32c4f0d6cf
commit 26d010d315
2 changed files with 55 additions and 42 deletions

View File

@ -515,12 +515,15 @@ class DevicePlugin(Plugin):
pass pass
# Dynamic control interface # Dynamic control interface
# All of these methods are called on the device_manager thread
def is_dynamically_controllable(self): def is_dynamically_controllable(self):
''' '''
Called by the device manager when starting plugins. If this method returns Called by the device manager when starting plugins. If this method returns
a string, then a) it supports the device manager's dynamic control 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 interface, and b) that name is to be used when talking to the plugin.
This method must be called from the device_manager thread.
''' '''
return None return None
@ -529,6 +532,8 @@ class DevicePlugin(Plugin):
This method is called to start the plugin. The plugin should begin This method is called to start the plugin. The plugin should begin
to accept device connections however it does that. If the plugin is to accept device connections however it does that. If the plugin is
already accepting connections, then do nothing. already accepting connections, then do nothing.
This method must be called from the device_manager thread.
''' '''
pass pass
@ -538,27 +543,35 @@ class DevicePlugin(Plugin):
accept connections, and should cleanup behind itself. It is likely that accept connections, and should cleanup behind itself. It is likely that
this method should call shutdown. If the plugin is already not accepting this method should call shutdown. If the plugin is already not accepting
connections, then do nothing. connections, then do nothing.
This method must be called from the device_manager thread.
''' '''
pass pass
def get_option(self, opt_string): def get_option(self, opt_string, default=None):
''' '''
Return the value of the option indicated by opt_string. This method can 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 be called when the plugin is not started. Return None if the option does
not exist. not exist.
This method must be called from the device_manager thread.
''' '''
return None return default
def set_option(self, opt_string, opt_value): def set_option(self, opt_string, opt_value):
''' '''
Set the value of the option indicated by opt_string. This method can Set the value of the option indicated by opt_string. This method can
be called when the plugin is not started. be called when the plugin is not started.
This method must be called from the device_manager thread.
''' '''
pass pass
def is_running(self): def is_running(self):
''' '''
Return True if the plugin is started, otherwise false Return True if the plugin is started, otherwise false
This method must be called from the device_manager thread.
''' '''
return False return False

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
# Imports {{{ # Imports {{{
import os, traceback, Queue, time, cStringIO, re, sys import os, traceback, Queue, time, cStringIO, re, sys
from threading import Thread from threading import Thread, Event
from PyQt4.Qt import (QMenu, QAction, QActionGroup, QIcon, SIGNAL, from PyQt4.Qt import (QMenu, QAction, QActionGroup, QIcon, SIGNAL,
Qt, pyqtSignal, QDialog, QObject, QVBoxLayout, Qt, pyqtSignal, QDialog, QObject, QVBoxLayout,
@ -30,6 +30,7 @@ from calibre.constants import DEBUG
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
from calibre.library.save_to_disk import find_plugboard from calibre.library.save_to_disk import find_plugboard
from calibre.gui2 import is_gui_thread
# }}} # }}}
class DeviceJob(BaseJob): # {{{ class DeviceJob(BaseJob): # {{{
@ -145,8 +146,10 @@ 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.devices_initialized = Event()
self.dynamic_plugins = {} self.dynamic_plugins = {}
self.dynamic_plugin_requests = Queue.Queue(0)
self.dynamic_plugin_responses = Queue.Queue(0)
def report_progress(self, *args): def report_progress(self, *args):
pass pass
@ -291,7 +294,7 @@ class DeviceManager(Thread): # {{{
n = d.is_dynamically_controllable() n = d.is_dynamically_controllable()
if n: if n:
self.dynamic_plugins[n] = d self.dynamic_plugins[n] = d
self.devices_initialized.put(None) self.devices_initialized.set()
while self.keep_going: while self.keep_going:
kls = None kls = None
@ -315,6 +318,7 @@ class DeviceManager(Thread): # {{{
traceback.print_exc() traceback.print_exc()
else: else:
self.detect_device() self.detect_device()
while True: while True:
job = self.next() job = self.next()
if job is not None: if job is not None:
@ -325,8 +329,15 @@ class DeviceManager(Thread): # {{{
self.current_job = None self.current_job = None
else: else:
break break
time.sleep(self.sleep_time) while True:
dynamic_method = None
try:
(dynamic_method, args, kwargs) = \
self.dynamic_plugin_requests.get(self.sleep_time)
res = dynamic_method(*args, **kwargs)
self.dynamic_plugin_responses.put(res)
except Queue.Empty:
break
# We are exiting. Call the shutdown method for each plugin # We are exiting. Call the shutdown method for each plugin
for p in self.devices: for p in self.devices:
try: try:
@ -516,47 +527,36 @@ class DeviceManager(Thread): # {{{
# dynamic plugin interface # dynamic plugin interface
def start_plugin(self, name): # This is a helper function that handles queueing with the device manager
def _queue_request(self, name, method, *args, **kwargs):
if not is_gui_thread():
raise ValueError(
'The device_manager dynamic plugin methods must be called from the GUI thread')
try: try:
d = self.dynamic_plugins.get(name, None) d = self.dynamic_plugins.get(name, None)
if d: self.dynamic_plugin_requests.put((getattr(d, method), args, kwargs))
d.start_plugin() return self.dynamic_plugin_responses.get()
except: except:
pass traceback.print_exc()
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 return None
# The dynamic plugin methods below must be called on the GUI thread. They
# will switch to the device thread before calling the plugin.
def start_plugin(self, name):
self._queue_request(name, 'start_plugin')
def stop_plugin(self, name):
self._queue_request(name, 'stop_plugin')
def get_option(self, name, opt_string, default=None):
return self._queue_request(name, 'get_option', opt_string, default=default)
def set_option(self, name, opt_string, opt_value): def set_option(self, name, opt_string, opt_value):
try: self._queue_request(name, 'set_option', opt_string, opt_value)
d = self.dynamic_plugins.get(name, None)
if d:
d.set_option(opt_string, opt_value)
except:
pass
def is_running(self, name): def is_running(self, name):
try: return self._queue_request(name, 'is_running')
d = self.dynamic_plugins.get(name, None)
if d:
return d.is_running()
except:
pass
return False
def is_enabled(self, name): def is_enabled(self, name):
try: try:
@ -767,7 +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() self.device_manager.devices_initialized.wait()
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'])