[Deskbar] Att. async module loaders: Handy test module



I was discussing with Raphael on IRC last night whether the async
loading/initializing was working. I wrote the attached test handler to
verify this. Async loading does indeed work, as you'll se if you stick
it in deskbar/handlers/.

Not that I don't expect you to have your own test handlers; this one was
just handy in putting Raphaels concerns to rest ;-P

The concerning fact was that the window was slow to come up. Digging
through the code I tracked this down to the signals emitted from the
ModuleLoader. I gave them the same priority as default GTK events.
Seemingly this was way to aggressive. Changing this to
gobject.PRIORITY_DEFAULT_IDLE did the trick. 

The attached patch is a modified version of Raphaels modification of my
initial patch. Ie. apply the *-fix_priority.patch after applying
*.patch.

Raphael:
You modifications to my initial patch are most welcome. They work out
fine for me... I didn't know that python had a thread safe Queue ready
to plug in. That is a valuable trick!


Cheers
Mikkel
from time import sleep
from handler import Handler
from handler import Match

NAME = "Test Module"
EXPORTED_CLASS = "TestModule"

class TestMatch (Match):
	def __init__(self, handler, name, icon=None):
		Match.__init__ (self, handler, name)
	
	def get_handler(self):
		"""
		Returns the handler owning this match.
		"""
		return self._handler
		
	def get_name(self, text=None):

		return {"name": self._name}
		
	def get_verb(self):
		return "%(name)s - %(text)s"
		
		
	def action(self, text=None):
		pass

class TestModule (Handler):

	INIT_TIME = 5

	def __init__ (self):
		Handler.__init__ (self, None)
		
	def initialize (self):
		print NAME + "initializing ... This takes %s secons." % self.INIT_TIME
		for i in range(self.INIT_TIME):
			print (i+1)*"."
			sleep (1)
		
	def query (self, qstring, max):
		if max > 0:
			return [TestMatch(self, "TestMatch")]
		else: return []
		
	def stop (self):
		pass
		
	def get_priority(self):	
		return 300
	
		
if __name__ == "__main__":
	print "Implement me."
Index: deskbar/applet.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/applet.py,v
retrieving revision 1.13
diff -u -p -r1.13 applet.py
--- deskbar/applet.py	10 Oct 2005 17:53:21 -0000	1.13
+++ deskbar/applet.py	10 Oct 2005 21:30:07 -0000
@@ -1,14 +1,21 @@
 import os, time
 import deskbar, deskbar.deskbarentry, deskbar.about, deskbar.preferences, deskbar.applet_keybinder
+from deskbar.module_list import ModuleLoader
 # WARNING: Load gnome.ui before gnomeapplet or we have a nasty warning.
 import gnome.ui
 import gnomeapplet, gtk, gtk.gdk, gconf
 
+module_dirs = [deskbar.HANDLERS_DIR, "~/.gnome2/deskbar-applet"]
+
 class DeskbarApplet:
 	def __init__(self, applet):
 		self.applet = applet
-						
-		self.entry = deskbar.deskbarentry.DeskbarEntry()
+		
+		self.loader = ModuleLoader (module_dirs)
+		self.loader.connect ("module-loaded", self.loader.initialize_module_async_cb)
+		self.loader.load_all_async ()
+		
+		self.entry = deskbar.deskbarentry.DeskbarEntry(self.loader)
 		self.entry.get_evbox().connect("button-press-event", self.on_icon_button_press)
 		self.entry.get_entry().connect("button-press-event", self.on_entry_button_press)
 
Index: deskbar/deskbar-applet
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/deskbar-applet,v
retrieving revision 1.6
diff -u -p -r1.6 deskbar-applet
--- deskbar/deskbar-applet	6 Oct 2005 16:36:17 -0000	1.6
+++ deskbar/deskbar-applet	10 Oct 2005 21:30:07 -0000
@@ -3,7 +3,11 @@
 # (C) 2005 Nigel Tao.
 # Licensed under the GNU GPL.
 
-import gtk, gnomeapplet
+import gtk
+gtk.threads_init()
+
+import gnomeapplet
+
 import getopt, sys
 from os.path import *
 
@@ -18,15 +22,6 @@ if _check(name):
 else:
 	print "Running installed deskbar, using normal PYTHONPATH"
 
-# Init twisted
-#from twisted.python import threadable
-#threadable.init()
-
-#from twisted.internet import gtk2reactor
-#gtk2reactor.install()
-
-#from twisted.internet import reactor
-
 # Now the path is set, import our applet
 import deskbar.applet	
 
@@ -36,18 +31,17 @@ def applet_factory(applet, iid):
 
 # Return a standalone window that holds the applet
 def build_window():
-	win = gtk.Window(gtk.WINDOW_TOPLEVEL)
-	win.set_title("Deskbar Applet")
-	#win.connect("destroy", lambda x: reactor.stop())
-	win.connect("destroy", gtk.main_quit)
+	app = gtk.Window(gtk.WINDOW_TOPLEVEL)
+	app.set_title("Deskbar Applet")
+	app.connect("destroy", gtk.main_quit)
 	
 	applet = gnomeapplet.Applet()
 	applet_factory(applet, None)
-	applet.reparent(win)
+	applet.reparent(app)
 		
-	win.show_all()
+	app.show_all()
 	
-	return win
+	return app
 		
 		
 def usage():
@@ -61,7 +55,7 @@ OPTIONS:
 	"""
 	sys.exit()
 	
-if __name__ == "__main__":
+if __name__ == "__main__":	
 	standalone = False
 	
 	try:
@@ -79,17 +73,18 @@ if __name__ == "__main__":
 			print "No problems so far."
 		elif o in ("-w", "--window"):
 			standalone = True
-			
+
 	if standalone:
 		build_window()
+		gtk.threads_enter()
 		gtk.main()
+		gtk.threads_leave()
 	else:
+		gtk.threads_enter()
 		gnomeapplet.bonobo_factory(
 			"OAFIID:Deskbar_Applet_Factory",
 			gnomeapplet.Applet.__gtype__,
 			"deskbar-applet",
 			"0",
 			applet_factory)
-
-	#reactor.suggestThreadPoolSize(3)
-	#reactor.run()
+		gtk.threads_leave()	
Index: deskbar/deskbarentry.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/deskbarentry.py,v
retrieving revision 1.11
diff -u -p -r1.11 deskbarentry.py
--- deskbar/deskbarentry.py	10 Oct 2005 17:53:21 -0000	1.11
+++ deskbar/deskbarentry.py	10 Oct 2005 21:30:07 -0000
@@ -3,14 +3,10 @@ import cgi
 
 import deskbar
 import deskbar.iconentry
+from deskbar.module_list import ModuleList
 
 import gtk, gobject
 
-from module_list import ModuleList
-from module_list import ModuleLoader
-module_dirs = [deskbar.HANDLERS_DIR, "~/.gnome2/deskbar-applet"]
-
-
 # The liststore columns
 HANDLER_PRIO_COL = 0
 MATCH_PRIO_COL = 1
@@ -31,12 +27,13 @@ MOVE_UP   = -1
 MOVE_DOWN = +1
 
 class DeskbarEntry(deskbar.iconentry.IconEntry):
-	def __init__(self):
+	def __init__(self, loader):
 		deskbar.iconentry.IconEntry.__init__(self)
 		
 		# Set up the Handlers
 		self._handlers = ModuleList ()
-		self._load_handlers()
+		loader.connect ("module-loaded", self._handlers.update_row_cb)
+		loader.connect ("module-initialized", self._handlers.module_toggled_cb)
 		
 		self._completion_model = None
 		self._selected_match_index = -1
@@ -103,12 +100,7 @@ class DeskbarEntry(deskbar.iconentry.Ico
 	
 	def get_history(self):
 		return self._history
-		
-	def _load_handlers(self):
-		loader = ModuleLoader (self._handlers, module_dirs, ".py")
-		loader.load_all ()
-		return 
-			
+					
 	def _on_sort_matches(self, treemodel, iter1, iter2):
 		# First compare global handler priority
 		diff = treemodel[iter1][HANDLER_PRIO_COL] - treemodel[iter2][HANDLER_PRIO_COL]
Index: deskbar/handler.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handler.py,v
retrieving revision 1.1
diff -u -p -r1.1 handler.py
--- deskbar/handler.py	6 Oct 2005 14:01:06 -0000	1.1
+++ deskbar/handler.py	10 Oct 2005 21:30:07 -0000
@@ -73,10 +73,14 @@ class Match:
 		
 class Handler:
 	def __init__(self, iconfile):
+		"""
+		The constructor of the Handler should generally not block. 
+		Heavy duty tasks such as indexing should be done in the initialize() method.
+		"""
 		# We load the icon file, and if it fails load an empty one
 		try:
-			self._icon = gtk.gdk.pixbuf_new_from_file_at_size(join(deskbar.ART_DATA_DIR, iconfile), -1, deskbar.ICON_SIZE)
-		except gobject.GError:
+			self._icon = gtk.gdk.pixbuf_new_from_file_at_size(join(deskbar.ART_DATA_DIR, iconfile), deskbar.ICON_SIZE, deskbar.ICON_SIZE)
+		except Exception:
 			self._icon = None
 		
 	def get_priority(self):
@@ -91,6 +95,25 @@ class Handler:
 		Returns None if there is no associated icon.
 		"""
 		return self._icon
+	
+	def initialize(self):
+		"""
+		The constructor of the Handler should generally not block. 
+		Heavy duty tasks such as indexing should be done in this method.
+		
+		Handler.initialize() is guarantied to be called before the handler
+		is queried.
+		"""
+		pass
+	
+	def stop(self):
+		"""
+		If the handler needs any cleaning up before it is unloaded, do it here.
+		
+		Handler.stop() is guarantied to be called before the handler is 
+		unloaded.
+		"""
+		pass
 		
 	def query(self, query, max=5):
 		"""
Index: deskbar/module_list.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/module_list.py,v
retrieving revision 1.3
diff -u -p -r1.3 module_list.py
--- deskbar/module_list.py	9 Oct 2005 20:58:36 -0000	1.3
+++ deskbar/module_list.py	10 Oct 2005 21:30:07 -0000
@@ -5,23 +5,27 @@ import sys
 import pydoc
 from os.path import join, basename, normpath, abspath
 from os.path import split, expanduser, exists, isfile, dirname
+from threading import Thread
+import Queue
 
 class ModuleContext:
 	"""A generic wrapper for any object stored in a ModuleList.
 	settings is unused at the moment.
-	"""
-	def __init__ (self, icon, enabled, name, module, settings, filename):
+	"""	
+	
+	def __init__ (self, icon, enabled, module, settings, filename, name, exported_class):
 		"""The icon should be a gtk.gdk.Pixbuf"""
 		self.icon = icon
 		self.enabled = enabled
-		self.name = name
 		self.module = module
 		self.settings = settings
 		self.filename = filename
+		self.name = name
+		self.exported_class = exported_class
 		
 
 class ModuleListIter : 
-	"""An iter type to iterate over the modules contexts in a ModuleList object.
+	"""An iter type to iterate over the of *enabled* module contexts in a ModuleList object.
 	This object is (typically) not used directly. See the documentation for ModuleList.
 			
 	For documentation on iters see: http://docs.python.org/lib/typeiter.html
@@ -37,15 +41,12 @@ class ModuleListIter : 
 		return self
 		
 	def next (self):
-		"""Return the next module in the ModuleList."""
+		"""Return the next *enabled* module in the ModuleList."""
 		try:
-			mod = ModuleContext (	self.owner.get_value (self.owner_iter, self.owner.ICON_COL), 
-						self.owner.get_value (self.owner_iter, self.owner.ENABLED_COL), 
-						self.owner.get_value (self.owner_iter, self.owner.NAME_COL), 
-						self.owner.get_value (self.owner_iter, self.owner.MODULE_COL), 
-						self.owner.get_value (self.owner_iter, self.owner.SETTINGS_COL), 
-						self.owner.get_value (self.owner_iter, self.owner.FILENAME_COL))
+			mod = self.owner.get_context_from_iter (self.owner_iter)
 			self.owner_iter = self.owner.iter_next (self.owner_iter)
+			if not mod.enabled: 
+				return self.next()
 		except TypeError:
 			raise StopIteration
 		return mod
@@ -58,7 +59,7 @@ class ModuleList (gtk.ListStore):
 		for modctx in modlist:
 			do_something (modctx)
 	
-	From this perspective the ModuleList stores ModuleContext (it actually doesnt),
+	From this perspective the ModuleList stores ModuleContexts (it actually doesnt),
 	so to utilize the modules you'll have to acces modctx.module.
 	
 	Note that the gtk.ListView extends the following classes:
@@ -68,32 +69,94 @@ class ModuleList (gtk.ListStore):
 		http://www.pygtk.org/pygtk2reference/class-gtkliststore.html
 	Note that
 	"""
+	
 	ICON_COL = 0
 	ENABLED_COL = 1
-	NAME_COL = 2
-	MODULE_COL = 3
-	SETTINGS_COL = 4
-	FILENAME_COL = 5
+	MODULE_COL = 2
+	SETTINGS_COL = 3
+	FILENAME_COL = 4
+	NAME_COL = 5
+	EXP_CLASS_COL = 6
 	
 	def __init__ (self):
 		gtk.ListStore.__init__ (self, 	gtk.gdk.Pixbuf, 
 						bool, 
-						gobject.TYPE_STRING, 
 						gobject.TYPE_PYOBJECT, 
 						gobject.TYPE_PYOBJECT, 
+						gobject.TYPE_STRING, 
+						gobject.TYPE_STRING,
 						gobject.TYPE_STRING)
 		
 	def __iter__ (self):
 		return ModuleListIter (self)
 	
-	def add (self, mod_context):
-		it = self.append ()
-		self.set (it, self.ICON_COL, mod_context.icon)
-		self.set (it, self.ENABLED_COL, mod_context.enabled)
-		self.set (it, self.NAME_COL, mod_context.name)
-		self.set (it, self.MODULE_COL, mod_context.module)
-		self.set (it, self.SETTINGS_COL, mod_context.settings)
-		self.set (it, self.FILENAME_COL, mod_context.filename)
+	def add (self, context):
+		"""Appends the module context to the list."""
+		self.update_row (context, iter)
+
+
+	def get_iter_from_context (self, modctx):
+		"""Returns a gtk.TreeIter pointing to the row containing the filename
+		modctx.filename. This should be uniqualy determined by the context.
+		
+		If the filename is not found return None.
+		
+		INVARIANT: ModuleContexts are uniquely determined by their .filename
+		"""
+	
+		iter = self.get_iter_first ()
+		while (iter is not None):
+			if self.get_value (iter, self.FILENAME_COL) == modctx.filename:
+				break
+			iter = self.iter_next (iter)
+		return iter
+
+	def get_context_from_iter (self, iter):
+		"""Return a ModuleContext representing the row pointed to by iter."""
+		modctx = ModuleContext (	self.get_value (iter, self.ICON_COL), 
+						self.get_value (iter, self.ENABLED_COL), 
+						self.get_value (iter, self.MODULE_COL), 
+						self.get_value (iter, self.SETTINGS_COL), 
+						self.get_value (iter, self.FILENAME_COL), 
+						self.get_value (iter, self.NAME_COL), 
+						self.get_value (iter, self.EXP_CLASS_COL))
+		return modctx
+
+	def update_row (self, context, iter=None):
+		"""If iter is set this method updates the row pointed to by iter with the 
+		values of context. 
+		
+		If iter is not set it will try to obtain an iter pointing
+		to the row containg context.filename. If there's no such row, it will append it.
+		"""
+		
+		if (iter is None):
+			iter = self.get_iter_from_context (context)
+		if (iter is None):
+			iter = self.append ()
+		
+		self.set_value(iter, self.ICON_COL, context.icon)
+		self.set_value(iter, self.ENABLED_COL, context.enabled)
+		self.set_value(iter, self.MODULE_COL, context.module)
+		self.set_value(iter, self.SETTINGS_COL, context.settings)
+		self.set_value(iter, self.FILENAME_COL, context.filename)
+		self.set_value(iter, self.NAME_COL, context.name)
+		self.set_value(iter, self.EXP_CLASS_COL, context.exported_class)
+		
+	def update_row_cb (self, sender, context, iter=None):
+		"""
+		Callback for updating the row containing context.
+		If iter is set the row to which it points to will be
+		updated with the context.
+		"""
+		self.update_row (context, iter)
+	
+	def module_toggled_cb (self, sender, context):
+		"""
+		Callback to toggle the enabled state of the context.
+		"""
+		iter = self.get_iter_from_context (context)
+		self.set_value(iter, self.ENABLED_COL, context.enabled)
 		
 class ModuleListView (gtk.TreeView):
 	"""A versatile list widget that displays the contents of a ModuleList.
@@ -107,6 +170,11 @@ class ModuleListView (gtk.TreeView):
 	This will construct a list showing the module names and a checkbox on whether or
 	not they are enabled.
 	"""
+	
+	__gsignals__ = {
+		"row-toggled" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT])
+	}
+	
 	def __init__ (self, model, columns):
 		gtk.TreeView.__init__ (self, model)
 		
@@ -119,7 +187,7 @@ class ModuleListView (gtk.TreeView):
 		if (model.ENABLED_COL in columns):
 			cell_enabled = gtk.CellRendererToggle ()
 			cell_enabled.set_property ("activatable", True)
-			cell_enabled.connect('toggled', self.toggle_enable, model)
+			cell_enabled.connect('toggled', self.emit_row_toggled, model)
 			self.column_enabled = gtk.TreeViewColumn ("Enabled", cell_enabled, active=model.ENABLED_COL)
 	
 		if (model.NAME_COL in columns):
@@ -139,30 +207,67 @@ class ModuleListView (gtk.TreeView):
 				
 		self.set_headers_visible(True)
 		
-	def toggle_enable (self, cell, path, model):
-		iter = model.get_iter(path)
-		model.set_value(iter, model.ENABLED_COL, not cell.get_active())
-		for mod in model:
-			print mod.name +" is enabled: " + str(mod.enabled)
-			
-class ModuleLoader:
+	def emit_row_toggled (self, cell, path, model):
+		"""Callback for the toggle buttons in the ModuleList.ENABLED_COL.
+		Emits a 'row-toggled' signal passing the context in the row as argument."""
+		context = model.get_context_from_iter (model.get_iter(path))
+		self.emit ("row-toggled", context)
+		
+
+
+class ModuleLoader (gobject.GObject):
 	"""An auxilary class to ModuleList. Create an instance of ModuleLoader by
 	specifying the which directories to search and what extension to accept.
 	The load_all() method will load all qualified modules into the ModuleList
 	specified in the constructor.
+	
+	Most methods have a _async variant. These methods emits signals that is handled
+	by the *main* thread. This ensures that locking mechanisms are unlikely to be 
+	needed.
+		
+	Hint: If you pass None as the dirs argument the ModuleLoader will not search
+	for modules at all. This is useful if you want to reload a single module for
+	which you know the path.
+	
+	Important: Remember to do gtk.gdk.threads_init() or gobject.threads_init() before
+	using any of the _async methods or else it WON'T WORK. Caveat emptor!
 	"""
-	def __init__ (self, modlist, dirs, extension=".py"):
-		"""modlist: The ModuleList to store all succesfully loaded modules
+	
+	__gsignals__ = {
+		"module-loaded" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]),
+		"module-initialized" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]),
+		"module-stopped" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT])
+	}
+	
+	def __init__ (self, dirs, extension=".py"):
+		"""
 		dirs: A list of directories to search. Relative pathnames and paths
-			  containing ~ will be expanded.
+			  containing ~ will be expanded. If dirs is None the 
+			  ModuleLoader will not search for modules.
 		extension: What extension should this ModuleLoader accept (string).
 		"""
-		self.modlist = modlist
-		self.dirs = map (lambda s: abspath(expanduser(s)), dirs)
+		gobject.GObject.__init__ (self)
 		self.ext = extension
-		self.filelist = self.build_filelist ()
-		
-			
+		if (dirs):
+			self.dirs = map (lambda s: abspath(expanduser(s)), dirs)
+			self.filelist = self.build_filelist ()
+		else:
+			self.dirs = None
+			self.filelist = []
+		
+		self.task_queue = Queue.Queue(0)
+		thread = Thread(None, self.consume_queue)
+		thread.setDaemon(True)
+		thread.start()
+	
+	def consume_queue(self):
+		while True:
+			print '------ In thread, getting task queue'
+			task = self.task_queue.get()
+			print '------ Got:', task
+			task()
+
+				
 	def build_filelist (self):
 		"""Returns a list containing the filenames of all qualified modules.
 		This method is automatically invoked by the constructor.
@@ -182,18 +287,11 @@ class ModuleLoader:
 		if (filename[-len(self.ext):] == self.ext):
 			return True
 		return False
-			
-	def load_all (self):
-		"""Tries to load all qualified modules detected by the ModuleLoader.
-		All succesfully loaded modules are stored in the ModuleList.
-		"""
-		for f in self.filelist:
-			self.load (f)
-	
-	def load (self, filename):
-		"""Tries to load the specified file as a module and stores it in the ModuleList."""
+				
+	def import_module (self, filename):
+		"""Tries to import the specified file. Returns the python module on succes.
+		Primarily for internal use."""
 		try:
-			print "Importing %s" % filename
 			mod = pydoc.importfile (filename)
 		except IOError, err:
 			print >> sys.stderr, "Error loading the file: %s\nThe file probably doesn't exist." % filename
@@ -203,7 +301,7 @@ class ModuleLoader:
 			print >> sys.stderr, "Unknown error loading the file: %s." % filename
 			print >> sys.stderr, str(err)
 			return
-				
+		
 		try:
 			if (mod.EXPORTED_CLASS): pass
 			if (mod.NAME): pass
@@ -217,42 +315,159 @@ class ModuleLoader:
 			return
 					
 		try:
-			mod_init = getattr (mod, mod.EXPORTED_CLASS)
+			if (getattr (mod, mod.EXPORTED_CLASS)) : pass
 		except AttributeError:
 			print >> sys.stderr, "Class %s not found in file %s. Skipping." % (mod.EXPORTED_CLASS, filename)
 			return
-								
+		
 		try:
-			mod_instance = mod_init()
-		except Exception, err:
-			print >> sys.stderr, "Error in file: %s" % filename
-			print >> sys.stderr, "There was an error initializing the class: %s" % str(mod_init)
-			print >> sys.stderr, str(err)
+			if (getattr(mod, mod.EXPORTED_CLASS).initialize) : pass
+		except AttributeError:
+			print >> sys.stderr, "Class %s in file %s does not have an initialize(self) method. Skipping." % (mod.EXPORTED_CLASS, filename)
 			return
-
-		context = ModuleContext (None, True, mod.NAME, mod_instance, None, filename)
-		self.modlist.add(context)
 			
+		return mod
+	
+	def load_all (self):
+		"""Tries to load all qualified modules detected by the ModuleLoader.
+		Each time a module is loaded it will emit a 'module-loaded' signal
+		passing a corresponding module context.
+		"""
+		if self.dirs is None:
+			print >> sys.stderr, "The ModuleLoader at %s has no filelist!" % str(id(self))
+			print >> sys.stderr, "It was probably initialized with dirs=None."
+			return
+			
+		for f in self.filelist:
+				self.load (f)
+	
+	def load (self, filename):
+		"""Loads the given file as a module and emits a 'module-loaded' signal
+		passing a corresponding ModuleContext as argument. 
+		
+		Returns the context as added to the list.
+		"""
+		mod = self.import_module (filename)
+		if mod is None : return
+		mod_instance = getattr (mod, mod.EXPORTED_CLASS) ()
+		
+		context = ModuleContext (mod_instance.get_icon(), False, mod_instance, 
+					None, filename, mod.NAME, mod.EXPORTED_CLASS)
+		gobject.idle_add (self.__emit_module_loaded, context, priority=gobject.PRIORITY_DEFAULT)
+		print "Loaded module '%s' from file %s." % (mod.NAME, filename)
+		return context
+					
+	def __emit_module_loaded (self, context):
+		"""Idle method. Internal use only."""
+		self.emit ("module-loaded", context)
+		return False
+			
+	def initialize_module (self, context):
+		"""
+		Initializes the module in the given context. Emits a 'module-initialized' signal
+		when done, passing the (now enabled) contextas argument.
+		"""
+		
+		print "Initializing '%s'" % context.name
+		context.module.initialize ()
+		
+		context.enabled = True
+		gobject.idle_add (self.__emit_module_initialized, context, priority=gobject.PRIORITY_DEFAULT)
+		
+	def initialize_module_async_cb (self, sender, context):
+		"""
+		Convenience callback used to initialize a module when it's loaded.
+		Use like:
+			loader.connect ("module-loaded", loader.initialize_module_async_cb)
+		"""
+		self.initialize_module_async (context)
+	
+	def __emit_module_initialized (self, context):
+		"""Idle method. Internal use only."""
+		self.emit ("module-initialized", context)
+		return False
+		
+	def stop_module (self, context):
+		"""
+		Stops the module an sets context.enabled = False. Furthermore the context.module
+		instance is also set to None. Emits a 'context-stopped' signal when done passing
+		the stopped context as argument.
+		"""
+		
+		print "Stopping '%s'" % context.name
+		context.module.stop ()
+		
+		context.enabled = False
+		context.module = None
+		gobject.idle_add (self.__emit_module_stopped, context, priority=gobject.PRIORITY_DEFAULT)
+	
+	def __emit_module_stopped (self, context):
+		"""Idle method. Internal use only."""
+		self.emit ("module-stopped", context)
+		return False
+		
+	def load_all_async (self):
+		"""
+		Same as load_all() except the loading is done in a separate thread.
+		"""
+		self.task_queue.put(self.load_all)
+	
+	def load_async (self, filename):
+		"""
+		Invokes load() in a new thread.
+		"""
+		self.task_queue.put( lambda: self.load(filename) )
+		
+	def initialize_module_async (self, context):
+		"""
+		Invokes initialize_module in a new thread.
+		"""
+		self.task_queue.put( lambda: self.initialize_module(context) )
+		
+	def stop_module_async (self, context):
+		"""
+		Invokes stop_module in a new thread.
+		"""
+		self.task_queue.put( lambda: self.stop_module(context) )
+
+def toggle_module (sender, context, ml):
+	"""Test function"""
+	if (context.enabled):
+		ml.stop_module_async (context)
+	else:
+		ml.initialize_module_async (context)
+
 if __name__ == "__main__":
-	"""A test suite for the Module* classes. Run from top level dir, 
+
+	"""A test suite for the Module* classes. Run from top level dir,
 	ie. from deskbar-applet/ run 'python deskbar/module_list.py'."""
 	
+	gtk.threads_init()
+	
 	name = join(dirname(__file__), '..')
 	print 'Changing PYTHONPATH'
 	sys.path.insert(0, abspath(name))
     
 	l = ModuleList ()    
-	ml = ModuleLoader (l, ["deskbar/handlers"], ".py")
+	ml = ModuleLoader (["deskbar/handlers"], ".py")
+	ml.connect ("module-loaded", l.update_row_cb)
+	ml.connect ("module-initialized", l.module_toggled_cb)
+	ml.connect ("module-stopped", l.module_toggled_cb)
 	
 	# Load all or just the directories handler. Uncomment to your liking
-	#ml.load_all ()
-	ml.load (abspath(expanduser("deskbar/handlers/directories.py")))
+	ml.load_all_async ()
+	#ml.load_async (abspath(expanduser("deskbar/testmod.py")))
+
 
 	lw = ModuleListView (l, [ModuleList.FILENAME_COL, ModuleList.NAME_COL, ModuleList.ENABLED_COL])
+	lw.connect ("row-toggled", toggle_module, ml)
+	
 	win = gtk.Window ()
 	win.connect ("destroy", gtk.main_quit)
 	win.add (lw)
 	win.show ()
 	lw.show ()
-
+	
+	gtk.threads_enter()
 	gtk.main ()
+	gtk.threads_leave()
Index: deskbar/handlers/epiphany.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/epiphany.py,v
retrieving revision 1.13
diff -u -p -r1.13 epiphany.py
--- deskbar/handlers/epiphany.py	10 Oct 2005 18:15:18 -0000	1.13
+++ deskbar/handlers/epiphany.py	10 Oct 2005 21:30:07 -0000
@@ -50,7 +50,8 @@ class EpiphanySmartMatch(EpiphanyMatch):
 class EpiphanyHandler(deskbar.handler.Handler):
 	def __init__(self):
 		deskbar.handler.Handler.__init__(self, "web-bookmark.png")
-		
+	
+	def initialize(self):
 		parser = EpiphanyBookmarksParser(self)
 		self._indexer = parser.get_indexer()
 		self._smart_bookmarks = parser.get_smart_bookmarks()
Index: deskbar/handlers/galago.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/galago.py,v
retrieving revision 1.2
diff -u -p -r1.2 galago.py
--- deskbar/handlers/galago.py	6 Oct 2005 14:01:06 -0000	1.2
+++ deskbar/handlers/galago.py	10 Oct 2005 21:30:07 -0000
@@ -4,8 +4,12 @@ import gnomevfs
 import deskbar, deskbar.indexer
 import deskbar.handler
 
-EXPORTED_CLASS = "GalagoHandler"
-NAME = _("Email and Address Book")
+#EXPORTED_CLASS = "GalagoHandler"
+#NAME = _("Email and Address Book")
+
+# FIXME: Waiting for python bindings of galago.
+EXPORTED_CLASS = None
+NAME = "Waiting for python bindings of galago. Should allow to send IM by typing name."
 
 PRIORITY = 150
 
@@ -22,18 +26,17 @@ class GalagoMatch(deskbar.handler.Match)
 	def get_verb(self):
 		return _("Send Email to <b>%(name)s</b>")
 		
-		
-	
 class GalagoHandler(deskbar.handler.Handler):
 	def __init__(self):
-		deskbar.handler.Handler.__init__(self, "mail.png")
-		
+		deskbar.handler.Handler.__init__(self, "mail.png")	
 		self._indexer = deskbar.indexer.Index()
-		
+	
+	def initialize(self):
 		# FIXME: Dummy entries		
-		self._indexer.add("William Gates III <billg microsoft com>", GalagoMatch(self, "William Gates III", "billg microsoft com"))
-		self._indexer.add("Steve Ballmer <steve microsoft com>", GalagoMatch(self, "Steve Ballmer", "steve microsoft com"))
-		self._indexer.add("Bill Joy <bjoy sun com>", GalagoMatch(self, "Bill Joy", "bjoy sun com"))
+		#self._indexer.add("William Gates III <billg microsoft com>", GalagoMatch(self, "William Gates III", "billg microsoft com"))
+		#self._indexer.add("Steve Ballmer <steve microsoft com>", GalagoMatch(self, "Steve Ballmer", "steve microsoft com"))
+		#self._indexer.add("Bill Joy <bjoy sun com>", GalagoMatch(self, "Bill Joy", "bjoy sun com"))
+		pass
 	
 	def get_priority(self):
 		return PRIORITY
Index: deskbar/handlers/gtkbookmarks.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/gtkbookmarks.py,v
retrieving revision 1.4
diff -u -p -r1.4 gtkbookmarks.py
--- deskbar/handlers/gtkbookmarks.py	6 Oct 2005 14:01:06 -0000	1.4
+++ deskbar/handlers/gtkbookmarks.py	10 Oct 2005 21:30:07 -0000
@@ -27,10 +27,10 @@ class GtkBookmarkHandler(deskbar.handler
 	def __init__(self):
 		deskbar.handler.Handler.__init__(self, "folder-bookmark.png")
 		
-		print 'Starting .gtkbookmarks file indexation'
 		self._locations = {}
+		
+	def initialize(self):
 		self._scan_bookmarks_files()
-		print '\tDone !'
 		
 	def get_priority(self):
 		return PRIORITY
Index: deskbar/handlers/mozilla.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/mozilla.py,v
retrieving revision 1.13
diff -u -p -r1.13 mozilla.py
--- deskbar/handlers/mozilla.py	10 Oct 2005 18:15:18 -0000	1.13
+++ deskbar/handlers/mozilla.py	10 Oct 2005 21:30:07 -0000
@@ -66,6 +66,10 @@ class MozillaHandler(deskbar.handler.Han
 	def __init__(self):
 		deskbar.handler.Handler.__init__(self, "web-bookmark.png")
 		
+		self._indexer = None
+		self._smart_bookmarks = None
+	
+	def initialize(self):
 		parser = MozillaBookmarksParser(self)
 		self._indexer = parser.get_indexer()
 		
@@ -108,13 +112,11 @@ class MozillaBookmarksParser(HTMLParser.
 		
 		self._indexer = deskbar.indexer.Index()
 		
-		print 'Starting mozilla/ff bookmarks indexation'
 		if USING_FIREFOX:
 			self._index_firefox()
 		else:
 			self._index_mozilla()
 		self.close()
-		print '\tDone !'
 		
 	def get_indexer(self):
 		"""
Index: deskbar/handlers/networkplaces.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/networkplaces.py,v
retrieving revision 1.1
diff -u -p -r1.1 networkplaces.py
--- deskbar/handlers/networkplaces.py	7 Oct 2005 19:54:45 -0000	1.1
+++ deskbar/handlers/networkplaces.py	10 Oct 2005 21:30:07 -0000
@@ -8,7 +8,6 @@ NAME = _("Network Places")
 
 PRIORITY = 150
 
-GCONF_CLIENT = gconf.client_get_default()
 NETWORK_PLACES_GCONF = '/desktop/gnome/connected_servers'
 
 icon_theme = gtk.icon_theme_get_default()
@@ -30,10 +29,10 @@ class NetworkPlacesHandler(deskbar.handl
 	def __init__(self):
 		deskbar.handler.Handler.__init__(self, "folder-bookmark.png")
 		
-		print 'Starting Network places file indexation'
 		self._indexer = deskbar.indexer.Index()
+		
+	def initialize(self):
 		self._scan_network_places()
-		print '\tDone !'
 		
 	def get_priority(self):
 		return PRIORITY
@@ -42,18 +41,19 @@ class NetworkPlacesHandler(deskbar.handl
 		return self._indexer.look_up(query)[:max]
 		
 	def _scan_network_places(self):
-		if not GCONF_CLIENT.dir_exists(NETWORK_PLACES_GCONF):
+		client = gconf.client_get_default()
+		if not client.dir_exists(NETWORK_PLACES_GCONF):
 			return
-			
-		dirs = GCONF_CLIENT.all_dirs(NETWORK_PLACES_GCONF)
+		
+		dirs = client.all_dirs(NETWORK_PLACES_GCONF)
 		for place in dirs:
 			try:
-				name = GCONF_CLIENT.get_string(place+"/display_name")
-				uri = GCONF_CLIENT.get_string(place+"/uri")
+				name = client.get_string(place+"/display_name")
+				uri = client.get_string(place+"/uri")
 				
 				pixbuf = None
 				try:
-					icon = GCONF_CLIENT.get_string(place+"/icon")
+					icon = client.get_string(place+"/icon")
 					pixbuf = icon_theme.load_icon(icon, deskbar.ICON_SIZE, gtk.ICON_LOOKUP_USE_BUILTIN)
 				except Exception, msg:
 					print 'Error:_scan_network_places:Cannot retreive icon:', msg
Index: deskbar/handlers/pathprograms.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/pathprograms.py,v
retrieving revision 1.2
diff -u -p -r1.2 pathprograms.py
--- deskbar/handlers/pathprograms.py	9 Oct 2005 20:14:21 -0000	1.2
+++ deskbar/handlers/pathprograms.py	10 Oct 2005 21:30:07 -0000
@@ -37,10 +37,10 @@ class PathProgramsHandler(deskbar.handle
 		deskbar.handler.Handler.__init__(self, "generic.png")
 		
 		self._programs = {}
-		print 'Starting PATH programs indexation'
+		
+	def initialize(self):
 		self._desktop_programs = self._scan_desktop_files()
 		self._scan_path()
-		print '\tDone !'
 	
 	def get_priority(self):
 		return PRIORITY
Index: deskbar/handlers/programs.py
===================================================================
RCS file: /cvs/gnome/deskbar-applet/deskbar/handlers/programs.py,v
retrieving revision 1.13
diff -u -p -r1.13 programs.py
--- deskbar/handlers/programs.py	9 Oct 2005 23:38:46 -0000	1.13
+++ deskbar/handlers/programs.py	10 Oct 2005 21:30:07 -0000
@@ -76,9 +76,9 @@ class ProgramsHandler(deskbar.handler.Ha
 		deskbar.handler.Handler.__init__(self, "generic.png")
 		
 		self._indexer = deskbar.indexer.Index()
-		print 'Starting .desktop file indexation'
+		
+	def initialize(self):
 		self._scan_desktop_files()
-		print '\tDone !'
 		
 	def get_priority(self):
 		return PRIORITY
diff -ruN ../deskbar-applet/deskbar/module_list.py deskbar/module_list.py
--- ../deskbar-applet/deskbar/module_list.py	2005-10-11 09:25:26.506141800 +0200
+++ deskbar/module_list.py	2005-10-11 09:27:56.511337544 +0200
@@ -239,6 +239,8 @@
 		"module-stopped" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT])
 	}
 	
+	SIGNAL_PRIORITY = gobject.PRIORITY_DEFAULT_IDLE
+	
 	def __init__ (self, dirs, extension=".py"):
 		"""
 		dirs: A list of directories to search. Relative pathnames and paths
@@ -353,7 +355,7 @@
 		
 		context = ModuleContext (mod_instance.get_icon(), False, mod_instance, 
 					None, filename, mod.NAME, mod.EXPORTED_CLASS)
-		gobject.idle_add (self.__emit_module_loaded, context, priority=gobject.PRIORITY_DEFAULT)
+		gobject.idle_add (self.__emit_module_loaded, context, priority=self.SIGNAL_PRIORITY)
 		print "Loaded module '%s' from file %s." % (mod.NAME, filename)
 		return context
 					
@@ -372,7 +374,7 @@
 		context.module.initialize ()
 		
 		context.enabled = True
-		gobject.idle_add (self.__emit_module_initialized, context, priority=gobject.PRIORITY_DEFAULT)
+		gobject.idle_add (self.__emit_module_initialized, context, priority=self.SIGNAL_PRIORITY)
 		
 	def initialize_module_async_cb (self, sender, context):
 		"""
@@ -399,7 +401,7 @@
 		
 		context.enabled = False
 		context.module = None
-		gobject.idle_add (self.__emit_module_stopped, context, priority=gobject.PRIORITY_DEFAULT)
+		gobject.idle_add (self.__emit_module_stopped, context, priority=self.SIGNAL_PRIORITY)
 	
 	def __emit_module_stopped (self, context):
 		"""Idle method. Internal use only."""


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]