Hi,

as my deskbar handler fixes are still not committed ;) here is once
again an updated patch. I still found lots of things that had to be
fixed, such as development files being categorized as 'normal' files.

Application launching is now independent of any user apps like
xdg-open and co. I am just launching the .desktop file with the trick
I learned from the new deskbar ;). This is now working also for
applications having the Terminal option set.

I finished to cleanup the code, moved some definitions to fit the new
code. Seems to run fine here.

As before the patch to the current svn tracker-handler and the
tracker-handler.py itself is attached. I hope there aren't that many
bugs left, anyone having tracker+deskbar: please test it ;).

Cheers, Marcus
diff --git a/python/deskbar-handler/tracker-handler.py b/python/deskbar-handler/tracker-handler.py
index 72fb72f..13a56d2 100644
--- a/python/deskbar-handler/tracker-handler.py
+++ b/python/deskbar-handler/tracker-handler.py
@@ -9,168 +9,157 @@ import gnome
 import gobject
 from gettext import gettext as _
 
-import re, cgi
+import re, cgi, sys
 import os.path
-import dbus
 
-import deskbar
+import deskbar, deskbar.Utils, deskbar.gnomedesktop
 from deskbar.Handler import SignallingHandler
 from deskbar.Match import Match
 
 #Edit this var for change the numer of output results
-MAX_RESULTS = 10
+MAX_RESULTS = 20
 
-def _check_requirements ():
-	try:
-		import dbus
-		try :
-			if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
-				import dbus.glib
-			
-			# Check that Tracker can be started via dbus activation, we will have trouble if it's not
-			bus = dbus.SessionBus()
-			proxy_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
-			dbus_iface = dbus.Interface(proxy_obj, 'org.freedesktop.DBus')
-			activatables = dbus_iface.ListActivatableNames()
-			if not "org.freedesktop.Tracker" in activatables:
-				return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Tracker is not activatable via dbus", None)	
-				
-		except:
-			return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Python dbus.glib bindings not found.", None)
-		return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
-	except:
-		return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Python dbus bindings not found.", None)
+# XXX: HANDLERS defined at end of file
 
-HANDLERS = {
-	"TrackerSearchHandler" : {
-		"name": "Search for files using Tracker Search Tool",
-		"description": _("Search all of your documents (using Tracker), as you type"),
-		"requirements" : _check_requirements,
+# For now description param it's not used
+TYPES = {
+	'Applications': {
+		'description': (_('Launch %s (%s)') % ('<b>%(name)s</b>', '%(app_name)s')),
+		'category': 'applications',
 	},
-	"TrackerLiveSearchHandler" : {
-		"name": "Search for files using Tracker(live result)",
-		"description": _("Search all of your documents (using Tracker live), as you type"),
-		"requirements" : _check_requirements,
-		"categories" : {
-			"develop"	: {	
-				"name": _("Development Files"),
-			},
-			"music"	: {	
-				"name": _("Music"),
-			},
-			"images"	: {	
-				"name": _("Images"),
-			},
-			"videos"	: {	
-				"name": _("Videos"),
-			},
-		},
+
+	'GaimConversations': {
+		'description': (_('See %s conversation\n%s %s\nfrom %s') % ('<b>%(proto)s</b>', '%(channel)s', '<b>%(conv_to)s</b>', '<i>%(time)s</i>')),
+		'category': 'conversations',
 	},
-}
 
-#For now description param it's not used
-TYPES = {
-	"Conversations"	: {
-		"description": (_("See  conversations %s") % "<i>%(publisher)s</i>" ) + "\n<b>%(base)s</b>",
-		"category": "conversations",
-		},
-	"Email"	: {
-		"description": (_("Email from %s") % "<i>%(publisher)s</i>" ) + "\n<b>%(title)s</b>",
-		"category": "emails",
-		"action" : "evolution %(uri)s",
-		"icon" : "stock_mail",
-		},
-	"Music"	: {
-		"description": _("Listen to music %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "music",
-		},	
-	"Documents" 	: {
-		"description": _("See document %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "documents",
-		},
-	"Development Files" 	: {
-		"description": _("Open file %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "develop",
-		},
-	"Images" 		: { 
-		"description": _("View image %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "images",
-		},
-	"Videos"	: {
-		"description": _("Watch video  %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "videos",
-		},
-	"Other Files"	: {
-		"description": _("Open file %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>"),
-		"category": "files",
-		},
-	"Extra"	: {
-		"description": _("See more result with t-s-t"),
-		},
+	'Email': {
+		'description': (_('Email from %s') % '<i>%(publisher)s</i>' ) + '\n<b>%(title)s</b>',
+		'category': 'emails',
+		'action': 'evolution %(uri)s',
+		'icon': 'stock_mail',
+	},
+
+	'Music': {
+		'description': _('Listen to music %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'music',
+	},
+
+	'Documents': {
+		'description': _('See document %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'documents',
+	},
+
+	'Development': {
+		'description': _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'develop',
+	},
+
+	'Images': {
+		'description': _('View image %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'images',
+	},
+
+	'Videos': {
+		'description': _('Watch video  %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'videos',
+	},
+
+	'Other Files': {
+		'description': _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
+		'category': 'files',
+	},
+
+	'Folders': {
+		'description': _('Open folder %s\n%s') % ('<b>%(name)s</b>', '<i>%(dir)s/%(name)s</i>'),
+		'category': 'folders',
+	},
+
+	'Extra': {
+		'description': _('See more result with Tracker Search Tool'),
+	},
 }
 
 
-#STATIC HANDLER---------------------------------
+
 
 class TrackerFileMatch (Match):
+
 	def __init__(self, backend, **args):
 		deskbar.Match.Match.__init__(self, backend, **args)
-		
+
 	def action(self, text=None):
-		gobject.spawn_async(["tracker-search-tool", self.name], flags=gobject.SPAWN_SEARCH_PATH)
-			
+		gobject.spawn_async(['tracker-search-tool', self.name], flags=gobject.SPAWN_SEARCH_PATH)
+
 	def get_verb(self):
-		return _("Search <b>"+self.name+"</b> with Tracker Search Tool")
-	
+		return _('Search %s with Tracker Search Tool') % ('<b>'+self.name+'</b>')
+
 	def get_category (self):
-		return "actions"
+		return 'actions'
+
+
+
 
 class TrackerSearchHandler(deskbar.Handler.Handler):
+
 	def __init__(self):
-		deskbar.Handler.Handler.__init__(self, ("system-search", "tracker"))
-				
+		deskbar.Handler.Handler.__init__(self, ('system-search', 'tracker'))
+
 	def query(self, query):
 		return [TrackerFileMatch(self, name=query)]
 
-#LIVE HANDLER---------------------------------
+	@staticmethod
+	def requirements ():
+		if deskbar.Utils.is_program_in_path ('tracker-search-tool'):
+			return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
+		return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'tracker-search-tool not found', None)
+
+
+
 
 class TrackerMoreMatch (Match):
-	def __init__(self, backend, qstring, category="files", **args):
+
+	def __init__(self, backend, qstring, category='files', **args):
 		Match.__init__(self, backend, **args)
-		self._icon = deskbar.Utils.load_icon("tracker")
+		self._icon = deskbar.Utils.load_icon('tracker')
 		self.qstring = qstring
 		self.category = category
 
 	def get_verb(self):
-		return TYPES["Extra"]["description"]
+		return TYPES['Extra']['description']
 
 	def get_category (self):
 		try:
-			return TYPES[self.category]["category"]
+			return TYPES[self.category]['category']
 		except:
-			pass
+			return "files"
 
 	def action(self, text=None):
-		gobject.spawn_async(["tracker-search-tool", self.qstring], flags=gobject.SPAWN_SEARCH_PATH)
-		
+		gobject.spawn_async(['tracker-search-tool', self.qstring], flags=gobject.SPAWN_SEARCH_PATH)
+
+
+
+
 class TrackerLiveFileMatch (Match):
+
 	def __init__(self, handler,result=None, **args):
-		Match.__init__ (self, handler,name=result["name"], **args)
+		Match.__init__ (self, handler,name=result['name'], **args)
 
 		self.result = result
 		self.fullpath = result['uri']
 		self.init_names()
-		
-		self.result["base"] = self.base
-		self.result["dir"] = self.dir
-		
+
+		self.result['base'] = self.base
+		self.result['dir'] = self.dir
+
 		# Set the match icon
 		try:
-			self._icon = deskbar.Utils.load_icon(TYPES[result['type']]["icon"])
+			self._icon = deskbar.Utils.load_icon(TYPES[result['type']]['icon'])
 		except:
-			self._icon = deskbar.Utils.load_icon_for_file(result['uri'])
-		
-		print result
+			if self.result.has_key ('icon'):
+				self._icon = deskbar.Utils.load_icon_for_desktop_icon (result ['icon'])
+			else:
+				self._icon = deskbar.Utils.load_icon_for_file(result['uri'])
 
 	def get_name(self, text=None):
 		try:
@@ -180,21 +169,25 @@ class TrackerLiveFileMatch (Match):
 
 	def get_verb(self):
 		try:
-			return TYPES[self.result["type"]]["description"]
+			return TYPES[self.result['type']]['description']
 		except:
-			return _("Open file %s\nin %s")	% ("<b>%(base)s</b>", "<i>%(dir)s</i>")
-	
+			return _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>')
+
 	def get_hash(self, text=None):
 		try:
+			if self.result ['type'] == 'Applications':
+				# return a name that matches the one returned by the Program handler of deskbar
+				return 'generic_' + self.result ['app_basename']
 			return self.result['uri']
 		except:
 			pass
 
 	def action(self, text=None):
-		if TYPES[self.result["type"]].has_key("action"):
-			cmd = TYPES[self.result["type"]]["action"]
+		if TYPES[self.result['type']].has_key('action'):
+			cmd = TYPES[self.result['type']]['action']
 			cmd = map(lambda arg : arg % self.result, cmd.split()) # we need this to handle spaces correctly
-			print "Opening Tracker hit with command:", cmd
+
+			print 'Opening Tracker hit with command:', cmd
 			try:
 				# deskbar >= 2.17
 				deskbar.Utils.spawn_async(cmd)
@@ -202,84 +195,237 @@ class TrackerLiveFileMatch (Match):
 				# deskbar <= 2.16
 				gobject.spawn_async(args, flags=gobject.SPAWN_SEARCH_PATH)
 		else:
-			try:
-				# deskbar >= 2.17
-				deskbar.Utils.url_show ("file://"+cgi.escape(self.result['uri']))
-			except AttributeError:
-				gnome.url_show("file://"+cgi.escape(self.result['uri']))
-			print "Opening Tracker hit:", self.result['uri']
-		
+			if self.result.has_key ('desktop'):
+				self.result['desktop'].launch([])
+			else:
+				try:
+					# deskbar >= 2.17
+					deskbar.Utils.url_show ('file://'+cgi.escape(self.result['uri']))
+				except AttributeError:
+					gnome.url_show('file://'+cgi.escape(self.result['uri']))
+				print 'Opening Tracker hit:', self.result['uri']
+
 	def get_category (self):
 		try:
-			return TYPES[self.result["type"]]["category"]
+			return TYPES[self.result['type']]['category']
 		except:
-			return "files"
+			return 'files'
 
 	def init_names (self):
 		#print "Parsing «%r»" % self.fullpath
 		dirname, filename = os.path.split(self.fullpath)
 		if filename == '': #We had a trailing slash
 			dirname, filename = os.path.split(dirname)
-		
+
 		#Reverse-tilde-expansion
 		home = os.path.normpath(os.path.expanduser('~'))
 		regexp = re.compile(r'^%s(/|$)' % re.escape(home))
 		dirname = re.sub(regexp, r'~\1', dirname)
-		
+
 		self.dir = dirname
 		self.base = filename
 
 
+
+
 class TrackerLiveSearchHandler(SignallingHandler):
+
 	def __init__(self):
-		SignallingHandler.__init__(self, "tracker")
-		bus = dbus.SessionBus()
-		self.tracker = bus.get_object('org.freedesktop.Tracker','/org/freedesktop/tracker')
-		self.search_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Search')
-		self.keywords_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Keywords')
-		self.files_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Files')
+		SignallingHandler.__init__(self, 'tracker')
+		# initing on search request, see self.query
+		self.tracker = self.search_iface = self.keywords_iface = self.files_iface = None
 		self.set_delay (500)
-		
+		self.conv_re = re.compile (r'^.*?/logs/([^/]+)/([^/]+)/([^/]+)/(.+?)\.(:?txt|html)$') # all, proto, account, to-whom, time
+
+	def handle_email_hits (self, info, output):
+		output['title'] = cgi.escape(info[3])
+		output['publisher'] = cgi.escape(info[4])
+
+	def handle_conversation_hits (self, info, output):
+		output ['uri'] = info [0]
+		m = self.conv_re.match (output['uri'])
+		output['channel']=_('with')
+		output['proto']=output['conv_from']=output['conv_to']=output['time']='' # XXX, never happened during tests
+		if m:
+			output['proto'] = m.group (1)
+			output['conv_from'] = m.group (2)
+			output['conv_to'] = m.group (3)
+			output['time'] = m.group (4)
+		if output['conv_to'].endswith ('.chat'):
+			output['channel'] = _('in channel')
+			output['conv_to'] = output['conv_to'].replace ('.chat','')
+		if output['proto'] == 'irc':
+			nick_server = output['conv_from'].split ('@')
+			if len (nick_server) > 1:
+				output['conv_to'] = '%s on %s' % (output['conv_to'], nick_server[1])
+		# escape those entities, purple uses this to escape / on jabber channel/user conversations
+		output['uri'] = output['uri'].replace ('%', '%25')
+		# escape irc channel prefixes, else the path name parsing of stops at '#' (this holds also for the icon search)
+		output['uri'] = output['uri'].replace ('#', '%23')
+
+	def handle_application_hits (self, info, output):
+		# print info
+		#   dbus.Array(
+		#   [
+		#     dbus.String(u'/usr/share/applications/gksu.desktop'), # TrackerUri  0
+		#     dbus.String(u'Applications'),                         # TrackerType 1
+		#     dbus.String(u'Application'),                          # DesktopType 2
+		#     dbus.String(u'Root Terminal'),                        # DesktopName 3
+		#     dbus.String(u'gksu /usr/bin/x-terminal-emulator'),    # DesktopExec 4
+		#     dbus.String(u'gksu-root-terminal')                    # DesktopIcon 5
+		#   ],
+		#   signature=dbus.Signature('s'))
+		# Strip %U or whatever arguments in Exec field
+		output['app_name'] = re.sub(r'%\w+', '', info [4]).strip ()
+		output['app_basename'] = cgi.escape (os.path.basename (output['app_name']))
+		output['app_name'] = cgi.escape (output['app_name'])
+		if output['app_basename'] == '': # strange // in app_name, e.g. nautilus burn:///
+			output['app_basename'] = output['app_name']
+		output['name'] = cgi.escape (info [3])
+		output['icon'] = cgi.escape (info [5])
+
+		desktop = parse_desktop_file (output['uri'])
+		if not desktop:
+			print >> sys.stderr, '*** Could not read .desktop file: %s' % info[0]
+		else:
+			output['desktop'] = desktop
+
 	def recieve_hits (self, qstring, hits, max):
 		matches = []
-		self.results = {}
-		
+		results = {}
+
 		for info in hits:
-			output = {} 
+			output = {}
+
 			output['name'] = os.path.basename(info[0])
 			output['uri'] = str(cgi.escape(info[0]))
 			output['type'] = info[1]
-			if TYPES.has_key(output['type']) == 0:
-				output['type'] = "Other Files"	
-			try:
-				self.results[output['type']].append(output)
-			except:
-				self.results[output['type']] = [output]
-			
-			if output["type"] == "Email":
-				output["title"] = cgi.escape(info[3])
-				output["publisher"] = cgi.escape(info[4])
-			
-		for key in self.results.keys():
-				for res in self.results[key][0:MAX_RESULTS]:
-					matches.append(TrackerLiveFileMatch(self,res))
-				#if len(self.results[key]) > MAX_RESULTS:
-				#	matches.append( TrackerMoreMatch(self,qstring,key) )
+
+			if not TYPES.has_key(output['type']):
+				output['type'] = 'Other Files'
+
+			if output['type'] == 'Email':
+				self.handle_email_hits (info, output)
+
+			elif output['type'] in ('GaimConversations', 'Conversations'):
+				self.handle_conversation_hits (info, output)
+
+			elif output['type'] == 'Applications':
+				self.handle_application_hits (info, output)
+
+			# applications are launched by .desktop file, if not readable: exclude
+			if output['type'] != 'Applications' or output.has_key ('desktop'):
+				try:
+					results[output['type']].append(output)
+				except:
+					results[output['type']] = [output]
+
+		for key in results.iterkeys ():
+			for res in results[key][0:MAX_RESULTS]:
+				matches.append(TrackerLiveFileMatch(self,res))
+
 		self.emit_query_ready(qstring, matches)
-		print "Tracker response for %s, - %s hits returned, %s shown" % (qstring, len(hits), len(matches))
-		
+		print 'Tracker response for %s; %d hits returned, %d shown' % (qstring, len(hits), len(matches))
+
 	def recieve_error (self, error):
-		print "*** Tracker dbus error:", error
-				
+		print >> sys.stderr, '*** Tracker dbus error:', error
+
 	def query (self, qstring, max):
-		if qstring.count("tag:") == 0: 
-			self.search_iface.TextDetailed (-1, "Files", qstring, 0,10, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
-			self.search_iface.TextDetailed (-1, "Emails", qstring, 0,10, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
-			print "Tracker query:", qstring
+		if not self.tracker:
+			try:
+				import dbus
+				bus = dbus.SessionBus()
+				self.tracker = bus.get_object('org.freedesktop.Tracker','/org/freedesktop/tracker')
+				self.search_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Search')
+				self.keywords_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Keywords')
+				self.files_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Files')
+			except:
+				print >> sys.stderr, 'DBus connection to tracker failed, check your settings.'
+				return
+		if qstring.count('tag:') == 0:
+			for service in ('Files', 'Emails', 'Conversations', 'Applications'):
+				self.search_iface.TextDetailed (-1, service, qstring, 0,10, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
+			print 'Tracker query:', qstring
 		else:
 			if self.tracker.GetVersion() == 502:
-				self.search_iface.Query(-1,"Files",["File.Format"],"",qstring.replace("tag:",""),"",False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
+				self.search_iface.Query(-1,'Files',['File.Format'],'',qstring.replace('tag:',''),'',False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
 			elif self.tracker.GetVersion() == 503:
-				self.search_iface.Query(-1,"Files",["File:Mime"],"",qstring.replace("tag:",""),"",False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
-			print "Tracker tag query:", qstring.replace("tag:","")
+				self.search_iface.Query(-1,'Files',['File:Mime'],'',qstring.replace('tag:',''),'',False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
+			print 'Tracker tag query:', qstring.replace('tag:','')
+
+	@staticmethod
+	def requirements ():
+		try:
+			import dbus
+			try :
+				if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
+					import dbus.glib
+
+				# Check that Tracker can be started via dbus activation, we will have trouble if it's not
+				bus = dbus.SessionBus()
+				proxy_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
+				dbus_iface = dbus.Interface(proxy_obj, 'org.freedesktop.DBus')
+				activatables = dbus_iface.ListActivatableNames()
+				if not 'org.freedesktop.Tracker' in activatables:
+					return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Tracker is not activatable via dbus', None)
+			except:
+				return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Python dbus.glib bindings not found.', None)
+			return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
+		except:
+			return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Python dbus bindings not found.', None)
+
+
+
+
+# this code is stolen from the programs handler of deskbar
+def parse_desktop_file(desktop, only_if_visible=False):
+	try:
+		desktop = deskbar.gnomedesktop.item_new_from_file(desktop, deskbar.gnomedesktop.LOAD_ONLY_IF_EXISTS)
+	except Exception, e:
+		print 'Couldn\'t read desktop file:%s:%s' % (desktop, e)
+		return None
+	if desktop == None or desktop.get_entry_type() != deskbar.gnomedesktop.TYPE_APPLICATION:
+		return None
+	if only_if_visible and desktop.get_boolean(deskbar.gnomedesktop.KEY_NO_DISPLAY):
+		return None
+	return desktop
+
+
 
+
+HANDLERS = {
+	'TrackerSearchHandler': {
+		'name': 'Search for files using Tracker Search Tool',
+		'description': _('Search all of your documents with Tracker Search Tool'),
+		'requirements': TrackerSearchHandler.requirements,
+	},
+
+	'TrackerLiveSearchHandler': {
+		'name': 'Search for files using Tracker(live result)',
+		'description': _('Search all of your documents (using Tracker live), as you type'),
+		'requirements': TrackerLiveSearchHandler.requirements,
+		'categories': {
+			'develop': {
+				'name': _('Development Files'),
+			},
+			'music': {
+				'name': _('Music'),
+			},
+			'images': {
+				'name': _('Images'),
+			},
+			'videos': {
+				'name': _('Videos'),
+			},
+			'conversations': {
+				'name': _('Conversations'),
+			},
+			'applications': {
+				'name': _('Applications'),
+			},
+			'folders': {
+				'name': _('Folders'),
+			},
+		},
+	},
+}
# -*- coding: utf-8 -*-
#    This handler was originaly created by Mikkel Kamstrup (c) 2006 and updated by Eugenio Cutolo (eulin)
#
#    This program can be distributed under the terms of the GNU GPL version 2 or later.
#    See the file COPYING.
#

import gnome
import gobject
from gettext import gettext as _

import re, cgi, sys
import os.path

import deskbar, deskbar.Utils, deskbar.gnomedesktop
from deskbar.Handler import SignallingHandler
from deskbar.Match import Match

#Edit this var for change the numer of output results
MAX_RESULTS = 20

# XXX: HANDLERS defined at end of file

# For now description param it's not used
TYPES = {
	'Applications': {
		'description': (_('Launch %s (%s)') % ('<b>%(name)s</b>', '%(app_name)s')),
		'category': 'applications',
	},

	'GaimConversations': {
		'description': (_('See %s conversation\n%s %s\nfrom %s') % ('<b>%(proto)s</b>', '%(channel)s', '<b>%(conv_to)s</b>', '<i>%(time)s</i>')),
		'category': 'conversations',
	},

	'Email': {
		'description': (_('Email from %s') % '<i>%(publisher)s</i>' ) + '\n<b>%(title)s</b>',
		'category': 'emails',
		'action': 'evolution %(uri)s',
		'icon': 'stock_mail',
	},

	'Music': {
		'description': _('Listen to music %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'music',
	},

	'Documents': {
		'description': _('See document %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'documents',
	},

	'Development': {
		'description': _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'develop',
	},

	'Images': {
		'description': _('View image %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'images',
	},

	'Videos': {
		'description': _('Watch video  %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'videos',
	},

	'Other Files': {
		'description': _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>'),
		'category': 'files',
	},

	'Folders': {
		'description': _('Open folder %s\n%s') % ('<b>%(name)s</b>', '<i>%(dir)s/%(name)s</i>'),
		'category': 'folders',
	},

	'Extra': {
		'description': _('See more result with Tracker Search Tool'),
	},
}




class TrackerFileMatch (Match):

	def __init__(self, backend, **args):
		deskbar.Match.Match.__init__(self, backend, **args)

	def action(self, text=None):
		gobject.spawn_async(['tracker-search-tool', self.name], flags=gobject.SPAWN_SEARCH_PATH)

	def get_verb(self):
		return _('Search %s with Tracker Search Tool') % ('<b>'+self.name+'</b>')

	def get_category (self):
		return 'actions'




class TrackerSearchHandler(deskbar.Handler.Handler):

	def __init__(self):
		deskbar.Handler.Handler.__init__(self, ('system-search', 'tracker'))

	def query(self, query):
		return [TrackerFileMatch(self, name=query)]

	@staticmethod
	def requirements ():
		if deskbar.Utils.is_program_in_path ('tracker-search-tool'):
			return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
		return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'tracker-search-tool not found', None)




class TrackerMoreMatch (Match):

	def __init__(self, backend, qstring, category='files', **args):
		Match.__init__(self, backend, **args)
		self._icon = deskbar.Utils.load_icon('tracker')
		self.qstring = qstring
		self.category = category

	def get_verb(self):
		return TYPES['Extra']['description']

	def get_category (self):
		try:
			return TYPES[self.category]['category']
		except:
			return "files"

	def action(self, text=None):
		gobject.spawn_async(['tracker-search-tool', self.qstring], flags=gobject.SPAWN_SEARCH_PATH)




class TrackerLiveFileMatch (Match):

	def __init__(self, handler,result=None, **args):
		Match.__init__ (self, handler,name=result['name'], **args)

		self.result = result
		self.fullpath = result['uri']
		self.init_names()

		self.result['base'] = self.base
		self.result['dir'] = self.dir

		# Set the match icon
		try:
			self._icon = deskbar.Utils.load_icon(TYPES[result['type']]['icon'])
		except:
			if self.result.has_key ('icon'):
				self._icon = deskbar.Utils.load_icon_for_desktop_icon (result ['icon'])
			else:
				self._icon = deskbar.Utils.load_icon_for_file(result['uri'])

	def get_name(self, text=None):
		try:
			return self.result
		except:
			pass

	def get_verb(self):
		try:
			return TYPES[self.result['type']]['description']
		except:
			return _('Open file %s\nin %s')	% ('<b>%(base)s</b>', '<i>%(dir)s</i>')

	def get_hash(self, text=None):
		try:
			if self.result ['type'] == 'Applications':
				# return a name that matches the one returned by the Program handler of deskbar
				return 'generic_' + self.result ['app_basename']
			return self.result['uri']
		except:
			pass

	def action(self, text=None):
		if TYPES[self.result['type']].has_key('action'):
			cmd = TYPES[self.result['type']]['action']
			cmd = map(lambda arg : arg % self.result, cmd.split()) # we need this to handle spaces correctly

			print 'Opening Tracker hit with command:', cmd
			try:
				# deskbar >= 2.17
				deskbar.Utils.spawn_async(cmd)
			except AttributeError:
				# deskbar <= 2.16
				gobject.spawn_async(args, flags=gobject.SPAWN_SEARCH_PATH)
		else:
			if self.result.has_key ('desktop'):
				self.result['desktop'].launch([])
			else:
				try:
					# deskbar >= 2.17
					deskbar.Utils.url_show ('file://'+cgi.escape(self.result['uri']))
				except AttributeError:
					gnome.url_show('file://'+cgi.escape(self.result['uri']))
				print 'Opening Tracker hit:', self.result['uri']

	def get_category (self):
		try:
			return TYPES[self.result['type']]['category']
		except:
			return 'files'

	def init_names (self):
		#print "Parsing «%r»" % self.fullpath
		dirname, filename = os.path.split(self.fullpath)
		if filename == '': #We had a trailing slash
			dirname, filename = os.path.split(dirname)

		#Reverse-tilde-expansion
		home = os.path.normpath(os.path.expanduser('~'))
		regexp = re.compile(r'^%s(/|$)' % re.escape(home))
		dirname = re.sub(regexp, r'~\1', dirname)

		self.dir = dirname
		self.base = filename




class TrackerLiveSearchHandler(SignallingHandler):

	def __init__(self):
		SignallingHandler.__init__(self, 'tracker')
		# initing on search request, see self.query
		self.tracker = self.search_iface = self.keywords_iface = self.files_iface = None
		self.set_delay (500)
		self.conv_re = re.compile (r'^.*?/logs/([^/]+)/([^/]+)/([^/]+)/(.+?)\.(:?txt|html)$') # all, proto, account, to-whom, time

	def handle_email_hits (self, info, output):
		output['title'] = cgi.escape(info[3])
		output['publisher'] = cgi.escape(info[4])

	def handle_conversation_hits (self, info, output):
		output ['uri'] = info [0]
		m = self.conv_re.match (output['uri'])
		output['channel']=_('with')
		output['proto']=output['conv_from']=output['conv_to']=output['time']='' # XXX, never happened during tests
		if m:
			output['proto'] = m.group (1)
			output['conv_from'] = m.group (2)
			output['conv_to'] = m.group (3)
			output['time'] = m.group (4)
		if output['conv_to'].endswith ('.chat'):
			output['channel'] = _('in channel')
			output['conv_to'] = output['conv_to'].replace ('.chat','')
		if output['proto'] == 'irc':
			nick_server = output['conv_from'].split ('@')
			if len (nick_server) > 1:
				output['conv_to'] = '%s on %s' % (output['conv_to'], nick_server[1])
		# escape those entities, purple uses this to escape / on jabber channel/user conversations
		output['uri'] = output['uri'].replace ('%', '%25')
		# escape irc channel prefixes, else the path name parsing of stops at '#' (this holds also for the icon search)
		output['uri'] = output['uri'].replace ('#', '%23')

	def handle_application_hits (self, info, output):
		# print info
		#   dbus.Array(
		#   [
		#     dbus.String(u'/usr/share/applications/gksu.desktop'), # TrackerUri  0
		#     dbus.String(u'Applications'),                         # TrackerType 1
		#     dbus.String(u'Application'),                          # DesktopType 2
		#     dbus.String(u'Root Terminal'),                        # DesktopName 3
		#     dbus.String(u'gksu /usr/bin/x-terminal-emulator'),    # DesktopExec 4
		#     dbus.String(u'gksu-root-terminal')                    # DesktopIcon 5
		#   ],
		#   signature=dbus.Signature('s'))
		# Strip %U or whatever arguments in Exec field
		output['app_name'] = re.sub(r'%\w+', '', info [4]).strip ()
		output['app_basename'] = cgi.escape (os.path.basename (output['app_name']))
		output['app_name'] = cgi.escape (output['app_name'])
		if output['app_basename'] == '': # strange // in app_name, e.g. nautilus burn:///
			output['app_basename'] = output['app_name']
		output['name'] = cgi.escape (info [3])
		output['icon'] = cgi.escape (info [5])

		desktop = parse_desktop_file (output['uri'])
		if not desktop:
			print >> sys.stderr, '*** Could not read .desktop file: %s' % info[0]
		else:
			output['desktop'] = desktop

	def recieve_hits (self, qstring, hits, max):
		matches = []
		results = {}

		for info in hits:
			output = {}

			output['name'] = os.path.basename(info[0])
			output['uri'] = str(cgi.escape(info[0]))
			output['type'] = info[1]

			if not TYPES.has_key(output['type']):
				output['type'] = 'Other Files'

			if output['type'] == 'Email':
				self.handle_email_hits (info, output)

			elif output['type'] in ('GaimConversations', 'Conversations'):
				self.handle_conversation_hits (info, output)

			elif output['type'] == 'Applications':
				self.handle_application_hits (info, output)

			# applications are launched by .desktop file, if not readable: exclude
			if output['type'] != 'Applications' or output.has_key ('desktop'):
				try:
					results[output['type']].append(output)
				except:
					results[output['type']] = [output]

		for key in results.iterkeys ():
			for res in results[key][0:MAX_RESULTS]:
				matches.append(TrackerLiveFileMatch(self,res))

		self.emit_query_ready(qstring, matches)
		print 'Tracker response for %s; %d hits returned, %d shown' % (qstring, len(hits), len(matches))

	def recieve_error (self, error):
		print >> sys.stderr, '*** Tracker dbus error:', error

	def query (self, qstring, max):
		if not self.tracker:
			try:
				import dbus
				bus = dbus.SessionBus()
				self.tracker = bus.get_object('org.freedesktop.Tracker','/org/freedesktop/tracker')
				self.search_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Search')
				self.keywords_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Keywords')
				self.files_iface = dbus.Interface(self.tracker, 'org.freedesktop.Tracker.Files')
			except:
				print >> sys.stderr, 'DBus connection to tracker failed, check your settings.'
				return
		if qstring.count('tag:') == 0:
			for service in ('Files', 'Emails', 'Conversations', 'Applications'):
				self.search_iface.TextDetailed (-1, service, qstring, 0,10, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
			print 'Tracker query:', qstring
		else:
			if self.tracker.GetVersion() == 502:
				self.search_iface.Query(-1,'Files',['File.Format'],'',qstring.replace('tag:',''),'',False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
			elif self.tracker.GetVersion() == 503:
				self.search_iface.Query(-1,'Files',['File:Mime'],'',qstring.replace('tag:',''),'',False,0,100, reply_handler=lambda hits : self.recieve_hits(qstring, hits, max), error_handler=self.recieve_error)
			print 'Tracker tag query:', qstring.replace('tag:','')

	@staticmethod
	def requirements ():
		try:
			import dbus
			try :
				if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
					import dbus.glib

				# Check that Tracker can be started via dbus activation, we will have trouble if it's not
				bus = dbus.SessionBus()
				proxy_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
				dbus_iface = dbus.Interface(proxy_obj, 'org.freedesktop.DBus')
				activatables = dbus_iface.ListActivatableNames()
				if not 'org.freedesktop.Tracker' in activatables:
					return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Tracker is not activatable via dbus', None)
			except:
				return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Python dbus.glib bindings not found.', None)
			return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
		except:
			return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, 'Python dbus bindings not found.', None)




# this code is stolen from the programs handler of deskbar
def parse_desktop_file(desktop, only_if_visible=False):
	try:
		desktop = deskbar.gnomedesktop.item_new_from_file(desktop, deskbar.gnomedesktop.LOAD_ONLY_IF_EXISTS)
	except Exception, e:
		print 'Couldn\'t read desktop file:%s:%s' % (desktop, e)
		return None
	if desktop == None or desktop.get_entry_type() != deskbar.gnomedesktop.TYPE_APPLICATION:
		return None
	if only_if_visible and desktop.get_boolean(deskbar.gnomedesktop.KEY_NO_DISPLAY):
		return None
	return desktop




HANDLERS = {
	'TrackerSearchHandler': {
		'name': 'Search for files using Tracker Search Tool',
		'description': _('Search all of your documents with Tracker Search Tool'),
		'requirements': TrackerSearchHandler.requirements,
	},

	'TrackerLiveSearchHandler': {
		'name': 'Search for files using Tracker(live result)',
		'description': _('Search all of your documents (using Tracker live), as you type'),
		'requirements': TrackerLiveSearchHandler.requirements,
		'categories': {
			'develop': {
				'name': _('Development Files'),
			},
			'music': {
				'name': _('Music'),
			},
			'images': {
				'name': _('Images'),
			},
			'videos': {
				'name': _('Videos'),
			},
			'conversations': {
				'name': _('Conversations'),
			},
			'applications': {
				'name': _('Applications'),
			},
			'folders': {
				'name': _('Folders'),
			},
		},
	},
}
_______________________________________________
tracker-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/tracker-list

Reply via email to