import cgi import deskbar import gnome import gobject import os.path import re import sys import time import urllib from gettext import gettext as _ # do the version dance! import deskbar _get_deskbar_version = lambda x: sum(map(lambda x: (10**(2*x[0]))*int(x[1]),zip((4,3,2,1),x.split('.')))) try: DESKBAR_VERSION = _get_deskbar_version (deskbar.VERSION) except: import deskbar.defs DESKBAR_VERSION = _get_deskbar_version (deskbar.defs.VERSION) MAX_RESULTS = 10 def dummy_escape (s): return s #return cgi.escape (s) # 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 def time_from_purple_log (instr): try: if instr.find ('+') != -1: # new kind of log timestamp... return time.strftime ('%c', time.strptime (re.sub (r'\+\d{4}', '', instr), '%Y-%m-%d.%H%M%S%Z')) else: # ...from ancient times return time.strftime ('%c', time.strptime (instr, '%Y-%m-%d.%H%M%S')) except: print >> sys.stderr, '*** time parsing for purple chat log failed: %s' % sys.exc_info ()[1] return instr class TSTHandlerImpl (object): def __init__(self): pass @staticmethod def has_requirements (): if DESKBAR_VERSION < _get_deskbar_version ('2.19'): 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) else: return deskbar.core.Utils.is_program_in_path ('tracker-search-tool') class TSTMatchImpl (object): def __init__ (self): pass def get_category (self): return 'actions' def get_hash (self, text=None): return 'tst-more-hits-action-'+self.name class LiveMatchImpl (object): def __init__ (self): pass def init_names (self): #print "Parsing ..." % 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 def wrap_get_name (self, text = None): return self.result def wrap_get_verb(self): try: return TYPES[self.result['type']]['description'] except: return _('Open file %s\nin %s') % ('%(base)s', '%(dir)s') def wrap_get_category (self): try: return TYPES[self.result['type']]['category'] except: return 'files' def wrap_get_hash(self, text=None): 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'] class LiveHandlerImpl (object): def __init__ (self): self.tracker = self.search_iface = self.keywords_iface = self.files_iface = None self.conv_re = re.compile (r'^.*?/logs/([^/]+)/([^/]+)/([^/]+)/(.+?)\.(:?txt|html)$') # all, proto, account, to-whom, time try: self.have_tst = deskbar.Utils.is_program_in_path ('tracker-search-tool') except: self.have_tst = deskbar.core.Utils.is_program_in_path ('tracker-search-tool') 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 ['name'] = output ['name'].replace ('%2b', '+') output ['uri'] = info [0] uri = info[0] m = self.conv_re.match (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'] = urllib.unquote (m.group (2)) output['conv_to'] = urllib.unquote (m.group (3)) output['time'] = time_from_purple_log (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]) output['uri'] = urllib.quote (output['uri']) # 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 receive_hits (self, qstring, hits, max): matches = [] for info in hits: output = {} output['name'] = os.path.basename(info[0]) output['uri'] = cgi.escape(str (info[0])) output['type'] = str (info[1]) if not TYPES.has_key(output['type']): output['type'] = 'Files' if output['type'] == 'Emails': 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'): if DESKBAR_VERSION < _get_deskbar_version ('2.19'): matches.append(TrackerLiveSearchMatch218 (self, output)) else: matches.append(TrackerLiveSearchMatch220 (output)) if len (matches): self.wrap_emit_query_ready(qstring, matches) print 'Tracker response for query "%s" (service %s); %s hits returned, %s shown' % (qstring, hits[0][1], len(hits), len(matches)) def recieve_error (self, error): print >> sys.stderr, '*** Tracker dbus error:', error def wrap_emit_query_ready (self, qstring, matches): try: self._emit_query_ready (qstring, matches) except: self.emit_query_ready (qstring, matches) def wrap_query (self, qstring, max=MAX_RESULTS): 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 for service in [key for key in TYPES.iterkeys () if key != 'Extra']: self.search_iface.TextDetailed (-1, service, qstring, 0, max, \ reply_handler = lambda hits: self.receive_hits (qstring, hits, max), \ error_handler = self.recieve_error) print 'Tracker query:', qstring if self.have_tst: if DESKBAR_VERSION < _get_deskbar_version ('2.19'): self.wrap_emit_query_ready (qstring, [TrackerSearchToolMatch218 (self, name=qstring)]) else: self.wrap_emit_query_ready (qstring, [TrackerSearchToolMatch220 (name=qstring, priority=self.get_priority ())]) TYPES = { 'Applications': { 'description': (_('Launch %s (%s)') % ('%(name)s', '%(app_name)s')), 'category': 'actions', #'icon': 'stock_run', }, 'GaimConversations': { 'description': (_('See %s conversation\n%s %s\nfrom %s') % ('%(proto)s', '%(channel)s', '%(conv_to)s', '%(time)s')), 'category': 'conversations', 'icon': 'stock_people', }, 'Emails': { 'description': (_('Email from %s') % '%(publisher)s' ) + '\n%(title)s', 'category': 'emails', 'action': 'evolution %(uri)s', 'icon': 'stock_mail', }, 'Music': { 'description': _('Listen to music %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'music', #'icon': 'audio', }, 'Documents': { 'description': _('See document %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'documents', #'icon': 'file', }, 'Development': { 'description': _('Open file %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'develop', #'icon': 'file', }, 'Images': { 'description': _('View image %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'images', 'icon': 'image', }, 'Videos': { 'description': _('Watch video %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'videos', 'icon': 'video', }, 'Files': { 'description': _('Open file %s\nin %s') % ('%(base)s', '%(dir)s'), 'category': 'files', #'icon': 'file', }, 'Folders': { 'description': _('Open folder %s\n%s') % ('%(name)s', '%(dir)s/%(name)s'), 'category': 'places', 'icon': 'stock_folder', }, 'Extra': { 'description': _('Search for %s with Tracker Search Tool') % ('%(name)s'), }, } # # # BEGIN DESKBAR < 2.19 CLASSES # # if DESKBAR_VERSION < _get_deskbar_version ('2.19'): import deskbar, deskbar.Utils, deskbar.gnomedesktop import deskbar.Handler import deskbar.Match class TrackerSearchToolMatch218 (deskbar.Match.Match, TSTMatchImpl): def __init__(self, backend, **args): deskbar.Match.Match.__init__(self, backend, **args) TSTMatchImpl.__init__ (self) def action(self, text=None): gobject.spawn_async(['tracker-search-tool', self.name], flags=gobject.SPAWN_SEARCH_PATH) def get_verb(self): return _('Search for %s with Tracker Search Tool') % '%(name)s' class TrackerSearchToolHandler218 (deskbar.Handler.Handler, TSTHandlerImpl): def __init__(self): deskbar.Handler.Handler.__init__(self, 'tracker') TSTHandlerImpl.__init__ (self) def query(self, query): return [TrackerSearchToolMatch218 (self, name=query)] class TrackerLiveSearchMatch218 (deskbar.Match.Match, LiveMatchImpl): def __init__(self, handler,result=None, **args): deskbar.Match.Match.__init__ (self, handler,name=result['name'], **args) LiveMatchImpl.__init__ (self) 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 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: # catch errors on gnome.url_show() 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'] except: print >> sys.stderr, '*** Could not open URL %s: %s' % (self.result['uri'], sys.exc_info ()[1]) def get_name (self, text = None): return self.wrap_get_name (text) def get_verb(self): return self.wrap_get_verb () def get_category (self): return self.wrap_get_category () def get_hash(self, text=None): return self.wrap_get_hash (text) class TrackerLiveSearchHandler218 (deskbar.Handler.SignallingHandler, LiveHandlerImpl): def __init__(self): deskbar.Handler.SignallingHandler.__init__(self, 'tracker') LiveHandlerImpl.__init__ (self) # initing on search request, see self.query self.set_delay (500) def query (self, qstring, max): self.wrap_query (qstring, max) @staticmethod def has_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) HANDLERS = { 'TrackerSearchToolHandler218': { 'name': 'Search for files using Tracker Search Tool', 'description': _('Search all of your documents with Tracker Search Tool'), #'requirements': TrackerSearchToolHandler.requirements, # XXX makes deskbar 2.18.1 not load the handler!! }, 'TrackerLiveSearchHandler218': { 'name': 'Search for files using Tracker', 'description': _('Search all of your documents, as you type'), 'requirements': TrackerLiveSearchHandler218.has_requirements, 'categories': { 'develop': { 'name': _('Development Files'), }, 'music': { 'name': _('Music'), }, 'images': { 'name': _('Images'), }, 'videos': { 'name': _('Videos'), }, 'conversations': { 'name': _('Conversations'), }, 'applications': { 'name': _('Applications'), }, 'places': { 'name': _('Places'), }, 'actions': { 'name': _('Actions'), }, }, }, } # # # BEGIN DESKBAR >= 2.19 CLASSES # # else: import deskbar.core.Utils import deskbar.core.gnomedesktop import deskbar.interfaces.Module import deskbar.interfaces.Match import deskbar.interfaces.Action from gettext import gettext as _ HANDLERS = ['TrackerSearchToolHandler220', 'TrackerLiveSearchHandler220'] class TrackerSearchToolMatch220 (deskbar.interfaces.Match, TSTMatchImpl): def __init__(self, **kwargs): deskbar.interfaces.Match.__init__(self, **kwargs) TSTMatchImpl.__init__ (self) self.name = kwargs['name'] self.add_action (TrackerSearchToolAction220 (kwargs['name'])) class TrackerSearchToolAction220 (deskbar.interfaces.Action): def __init__(self, name): deskbar.interfaces.Action.__init__ (self, name) self.name = name def activate(self, text=None): gobject.spawn_async(['tracker-search-tool', self.name], \ flags=gobject.SPAWN_SEARCH_PATH) def get_verb(self): return _('Search for %s with Tracker Search Tool') % '%(name)s' def get_hash (self): return 't-s-t:'+self.name class TrackerSearchToolHandler220 (deskbar.interfaces.Module, TSTHandlerImpl): INFOS = { 'icon': deskbar.core.Utils.load_icon ('tracker'), 'name': _('Tracker Search'), 'description': _('Search with Tracker Search Tool'), } def __init__(self): deskbar.interfaces.Module.__init__(self) TSTHandlerImpl.__init__ (self) def query(self, query): self._emit_query_ready (query, [TrackerSearchToolMatch220(name=query, priority=self.get_priority ())]) class TrackerLiveSearchMatch220 (deskbar.interfaces.Match, LiveMatchImpl): def __init__(self, result, **args): deskbar.interfaces.Match.__init__ (self) LiveMatchImpl.__init__ (self) self.result = result try: desktop = result['desktop'] del result['desktop'] except: desktop = None # Set the match icon try: self._pixbuf = deskbar.core.Utils.load_icon(TYPES[result['type']]['icon']) except: if self.result.has_key ('icon'): self._pixbuf = deskbar.core.Utils.load_icon_for_desktop_icon (result ['icon']) else: if self.result['type'] != 'Conversations': try: self._pixbuf = deskbar.core.Utils.load_icon ('file://'+result['uri']) except: pass # some icons cannot be loaded... (e.g. for non existent file or illegal URI) self.add_action (TrackerLiveSearchAction220 (result, desktop)) #if result['type'] == 'Images': # for prg in ('gimp', 'gthumb', 'gwenview'): # if is_program_in_path (prg): # self.add_action (OpenWithApplicationAction (result['name'], prg, [result['uri']])) def get_name (self, text = None): return self.result ['name'] def get_verb(self): return self.wrap_get_verb () def get_category (self): return self.wrap_get_category () def get_hash(self, text=None): return self.wrap_get_hash (text) class TrackerLiveSearchAction220 (deskbar.interfaces.Action, LiveMatchImpl): def __init__ (self, result, desktop): deskbar.interfaces.Action (self) LiveMatchImpl.__init__ (self) self.name = result['name'] self.desktop = desktop self.result = result self.fullpath = result['uri'] self.init_names() self.result['base'] = self.base self.result['dir'] = self.dir def get_name(self, text=None): return self.wrap_get_name (text) def get_hash(self, text=None): return self.wrap_get_hash (text) def get_verb(self): return self.wrap_get_verb () def activate (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 deskbar.core.Utils.spawn_async(cmd) else: if self.desktop: self.desktop.launch ([]) else: deskbar.core.Utils.url_show ('file://'+dummy_escape(self.result['uri'])) print 'Opening Tracker hit:', self.result['uri'] class TrackerLiveSearchHandler220 (deskbar.interfaces.Module, LiveHandlerImpl): INFOS = { 'icon': deskbar.core.Utils.load_icon ('tracker'), 'name': _('Tracker Live Search'), 'description': _('Search with Tracker, as you type'), 'categories': { 'develop': { 'name': _('Development Files'), }, 'music': { 'name': _('Music'), }, 'images': { 'name': _('Images'), }, 'videos': { 'name': _('Videos'), }, 'folders': { 'name': _('Folders'), }, }, } def __init__(self): deskbar.interfaces.Module.__init__(self) LiveHandlerImpl.__init__ (self) def query (self, qstring): return self.wrap_query (qstring, MAX_RESULTS) @staticmethod def has_prerequisites (): 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: TrackerLiveSearchHandler220.INSTRUCTIONS = ('Tracker is not activatable via dbus') return False except: TrackerLiveSearchHandler220.INSTRUCTIONS = ('Python dbus.glib bindings not found.') return False return True except: TrackerLiveSearchHandler220.INSTRUCTIONS = ('Python dbus bindings not found.') return False