# -*- 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 dbus
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
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)
HANDLERS = {
"TrackerSearchHandler" : {
"name": "Search for files using Tracker Search Tool",
"description": _("Search all of your documents (using Tracker), as you type"),
"requirements" : _check_requirements,
},
"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"),
},
"conversations" : {
"name": _("Conversations"),
},
"applications" : {
"name": _("Applications"),
},
"files": {
"name": _("Files"),
},
"documents": {
"name": _("Documents"),
},
},
},
}
#For now description param it's not used
TYPES = {
"Applications" : {
"description": (_("Launch %s (%s)") % ("%(name)s", "%(app_name)s") ),
"category": "applications",
"action": "%(exec)s",
},
"GaimConversations" : {
"description": (_("See %s conversation\n%s %s\nfrom %s") % ("%(proto)s", "%(channel)s", "%(conv_to)s", "%(time)s")),
"category": "conversations",
},
"Email" : {
"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",
},
"Documents" : {
"description": _("See document %s\nin %s") % ("%(base)s", "%(dir)s"),
"category": "documents",
},
"Development Files" : {
"description": _("Open file %s\nin %s") % ("%(base)s", "%(dir)s"),
"category": "develop",
},
"Images" : {
"description": _("View image %s\nin %s") % ("%(base)s", "%(dir)s"),
"category": "images",
},
"Videos" : {
"description": _("Watch video %s\nin %s") % ("%(base)s", "%(dir)s"),
"category": "videos",
},
"Other Files" : {
"description": _("Open file %s\nin %s") % ("%(base)s", "%(dir)s"),
"category": "files",
},
"Extra" : {
"description": _("See more result with t-s-t"),
},
}
#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)
def get_verb(self):
return _("Search "+self.name+" with Tracker Search Tool")
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)]
#LIVE HANDLER---------------------------------
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:
pass
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") % ("%(base)s", "%(dir)s")
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
# fix spaces in the first arg - needed for some desktop applications,
# notably nautilus burner or "gksu terminal" (parsed from .desktop file)
if len (cmd) > 1:
cmd = cmd[0].split () + cmd[1:]
else:
cmd = cmd[0].split ()
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:
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):
import re
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
#if deskbar.Utils.is_program_in_path ("xdg-open"):
# TYPES["Applications"]["action"] = "xdg-open %(uri)s"
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:
# XXX: terminal applications make only sense when run with xdg-open or
# the desktop-specific variant of it
terminal_only = desktop.get_string ('Terminal')
if not terminal_only or terminal_only.lower () == 'false':
# Strip %U or whatever arguments in Exec field
exe = re.sub ("%\w+", "", desktop.get_string("Exec"))
output['exec'] = cgi.escape (exe.strip ())
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)
# if Application and no key exec -> desktop file could no be read/parsed
if output['type'] != 'Applications' or output.has_key ('exec'):
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:
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:","")
# 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