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



Hmmm... This patch adds threads_{enter,leave} around all task executed
by the ModuleLoader daemon thread.

The UI still loads fast, but is irresponsive during long running tasks
in the daemon.

IMO this is the best and safest way of keeping thread safety without
doing fine grained locking - which would have to be implemented in each
handler separately...

So "We Don't Serve Dead Beef Anymore Sir"... (or so I hope)

Cheers
Mikkel
diff -ruN ../deskbar-applet/deskbar/applet.py deskbar/applet.py
--- ../deskbar-applet/deskbar/applet.py	2005-10-10 19:53:21.000000000 +0200
+++ deskbar/applet.py	2005-10-12 21:50:37.322381696 +0200
@@ -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", lambda l, mod: self.loader.initialize_module_async(mod))
+		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)
 
diff -ruN ../deskbar-applet/deskbar/deskbar-applet deskbar/deskbar-applet
--- ../deskbar-applet/deskbar/deskbar-applet	2005-10-06 18:36:17.000000000 +0200
+++ deskbar/deskbar-applet	2005-10-12 21:50:37.324381392 +0200
@@ -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 @@
 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 @@
 
 # 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 @@
 	"""
 	sys.exit()
 	
-if __name__ == "__main__":
+if __name__ == "__main__":	
 	standalone = False
 	
 	try:
@@ -81,8 +75,10 @@
 			standalone = True
 			
 	if standalone:
+		gtk.threads_enter()
 		build_window()
 		gtk.main()
+		gtk.threads_leave()	
 	else:
 		gnomeapplet.bonobo_factory(
 			"OAFIID:Deskbar_Applet_Factory",
@@ -90,6 +86,3 @@
 			"deskbar-applet",
 			"0",
 			applet_factory)
-
-	#reactor.suggestThreadPoolSize(3)
-	#reactor.run()
diff -ruN ../deskbar-applet/deskbar/deskbarentry.py deskbar/deskbarentry.py
--- ../deskbar-applet/deskbar/deskbarentry.py	2005-10-11 21:50:50.000000000 +0200
+++ deskbar/deskbarentry.py	2005-10-12 21:50:37.327380936 +0200
@@ -3,6 +3,7 @@
 
 import deskbar
 import deskbar.iconentry
+from deskbar.module_list import ModuleList
 
 import gtk, gobject
 
@@ -10,7 +11,6 @@
 from deskbar.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 +31,13 @@
 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 +104,7 @@
 	
 	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]
diff -ruN ../deskbar-applet/deskbar/handler.py deskbar/handler.py
--- ../deskbar-applet/deskbar/handler.py	2005-10-11 21:50:50.000000000 +0200
+++ deskbar/handler.py	2005-10-12 21:50:37.329380632 +0200
@@ -73,10 +73,14 @@
 		
 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), deskbar.ICON_SIZE, deskbar.ICON_SIZE)
-		except gobject.GError:
+		except Exception:
 			self._icon = None
 		
 	def get_priority(self):
@@ -91,6 +95,25 @@
 		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):
 		"""
diff -ruN ../deskbar-applet/deskbar/handlers/debug-blockinginit.py deskbar/handlers/debug-blockinginit.py
--- ../deskbar-applet/deskbar/handlers/debug-blockinginit.py	1970-01-01 01:00:00.000000000 +0100
+++ deskbar/handlers/debug-blockinginit.py	2005-10-12 21:50:37.337379416 +0200
@@ -0,0 +1,38 @@
+from time import sleep
+from handler import Handler
+from handler import Match
+
+NAME = "Debug: Blocking Init Module"
+EXPORTED_CLASS = "DebugBlockingInitModule"
+
+INIT_TIME = 5
+
+class DebugBlockingInitMatch(Match):
+	def __init__(self, handler, name, icon=None):
+		Match.__init__ (self, handler, name)
+	
+	def get_verb(self):
+		return "%(name)s - %(text)s"
+		
+	def action(self, text=None):
+		pass
+
+class DebugBlockingInitModule(Handler):
+	def __init__ (self):
+		Handler.__init__ (self, None)
+		
+	def initialize (self):
+		print NAME + " initializing ... This will block for %s seconds." % INIT_TIME
+		for i in range(INIT_TIME):
+			print (i+1)*"."
+			sleep (1)
+		
+	def query (self, qstring, max):
+		if max > 0:
+			return [DebugBlockingInitMatch(self, "TestMatch")]
+		else:
+			return []
+		
+	def get_priority(self):
+		return 0
+	
diff -ruN ../deskbar-applet/deskbar/handlers/epiphany.py deskbar/handlers/epiphany.py
--- ../deskbar-applet/deskbar/handlers/epiphany.py	2005-10-10 20:15:18.000000000 +0200
+++ deskbar/handlers/epiphany.py	2005-10-12 21:50:37.339379112 +0200
@@ -50,7 +50,8 @@
 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()
diff -ruN ../deskbar-applet/deskbar/handlers/galago.py deskbar/handlers/galago.py
--- ../deskbar-applet/deskbar/handlers/galago.py	2005-10-11 21:50:50.000000000 +0200
+++ deskbar/handlers/galago.py	2005-10-12 21:50:37.341378808 +0200
@@ -31,10 +31,12 @@
 		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"))
+		pass
 	
 	def get_priority(self):
 		return PRIORITY
diff -ruN ../deskbar-applet/deskbar/handlers/gtkbookmarks.py deskbar/handlers/gtkbookmarks.py
--- ../deskbar-applet/deskbar/handlers/gtkbookmarks.py	2005-10-06 16:01:06.000000000 +0200
+++ deskbar/handlers/gtkbookmarks.py	2005-10-12 21:50:37.343378504 +0200
@@ -27,10 +27,10 @@
 	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
diff -ruN ../deskbar-applet/deskbar/handlers/Makefile.am deskbar/handlers/Makefile.am
--- ../deskbar-applet/deskbar/handlers/Makefile.am	2005-10-11 22:37:49.000000000 +0200
+++ deskbar/handlers/Makefile.am	2005-10-12 21:50:37.335379720 +0200
@@ -12,3 +12,6 @@
 	programs.py \
 	pathprograms.py \
 	web_address.py
+
+EXTRA_DIST = \
+	debug-blockinginit.py
diff -ruN ../deskbar-applet/deskbar/handlers/mozilla.py deskbar/handlers/mozilla.py
--- ../deskbar-applet/deskbar/handlers/mozilla.py	2005-10-11 22:37:49.000000000 +0200
+++ deskbar/handlers/mozilla.py	2005-10-12 21:50:37.346378048 +0200
@@ -66,6 +66,10 @@
 	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 @@
 		
 		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):
 		"""
diff -ruN ../deskbar-applet/deskbar/handlers/networkplaces.py deskbar/handlers/networkplaces.py
--- ../deskbar-applet/deskbar/handlers/networkplaces.py	2005-10-07 21:54:45.000000000 +0200
+++ deskbar/handlers/networkplaces.py	2005-10-12 21:50:37.348377744 +0200
@@ -8,7 +8,6 @@
 
 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 @@
 	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 @@
 		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
diff -ruN ../deskbar-applet/deskbar/handlers/pathprograms.py deskbar/handlers/pathprograms.py
--- ../deskbar-applet/deskbar/handlers/pathprograms.py	2005-10-09 22:14:21.000000000 +0200
+++ deskbar/handlers/pathprograms.py	2005-10-12 21:50:37.351377288 +0200
@@ -37,10 +37,10 @@
 		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
diff -ruN ../deskbar-applet/deskbar/handlers/programs.py deskbar/handlers/programs.py
--- ../deskbar-applet/deskbar/handlers/programs.py	2005-10-11 22:37:49.000000000 +0200
+++ deskbar/handlers/programs.py	2005-10-12 21:50:37.353376984 +0200
@@ -82,9 +82,9 @@
 		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-09 22:58:36.000000000 +0200
+++ deskbar/module_list.py	2005-10-12 21:51:35.627517968 +0200
@@ -5,23 +5,27 @@
 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 @@
 		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 @@
 		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,93 @@
 		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.
+		"""
+		self.set_value(self.get_iter_from_context (context), self.ENABLED_COL, context.enabled)
 		
 class ModuleListView (gtk.TreeView):
 	"""A versatile list widget that displays the contents of a ModuleList.
@@ -107,6 +169,11 @@
 	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 +186,7 @@
 		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 +206,67 @@
 				
 		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."""
+		self.emit ("row-toggled", model.get_context_from_iter (model.get_iter(path)))
+		
+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)
+		# WE don't want the queue thread to prevent us from exiting
+		thread.setDaemon(True)
+		thread.start()
+	
+	def consume_queue(self):
+		while True:
+			try:
+				task = self.task_queue.get()
+				gtk.threads_enter ()
+				task ()
+				gtk.threads_leave ()
+			except Exception, msg:
+				print 'Error:consume_queue:Got an error while processing item:', msg
+				
 	def build_filelist (self):
 		"""Returns a list containing the filenames of all qualified modules.
 		This method is automatically invoked by the constructor.
@@ -179,21 +283,12 @@
 			
 	def is_module (self, filename):
 		"""Tests whether the filename has the appropriate extension."""
-		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."""
+		return (filename[-len(self.ext):] == self.ext)
+				
+	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 +298,7 @@
 			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
@@ -213,46 +308,139 @@
 			return
 		
 		if mod.EXPORTED_CLASS == None:
-			print >> sys.stderr, "The file %s decided to not load itself: %s" % (mod.NAME, filename)
+			print >> sys.stderr, "***"
+			print >> sys.stderr, "*** The file %s decided to not load itself: %s" % (mod.NAME, filename)
+			print >> sys.stderr, "***"
 			return
-					
+		
 		try:
-			mod_init = getattr (mod, mod.EXPORTED_CLASS)
+			if (getattr(mod, mod.EXPORTED_CLASS).initialize) : pass
 		except AttributeError:
-			print >> sys.stderr, "Class %s not found in file %s. Skipping." % (mod.EXPORTED_CLASS, filename)
+			print >> sys.stderr, "Class %s in file %s does not have an initialize(self) method. 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)
+			
+		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
-
-		context = ModuleContext (None, True, mod.NAME, mod_instance, None, filename)
-		self.modlist.add(context)
 			
+		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
+		
+		print "Loading module '%s' from file %s." % (mod.NAME, filename)
+		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)
+		
+		return context
+							
+	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)
+	
+	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)
+			
+	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()


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