home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / deskbar-applet / handlers / beagle-live.py next >
Encoding:
Python Source  |  2007-04-09  |  12.0 KB  |  389 lines

  1. import os, sys, cgi, re
  2. import gobject,gtk, gnome, gnome.ui, gnomevfs
  3. import deskbar, deskbar.Handler, deskbar.Utils, deskbar.Match
  4. from gettext import gettext as _
  5. from os.path import exists, dirname
  6. from deskbar.defs import VERSION
  7. from deskbar.Utils import is_program_in_path, spawn_async, url_show, url_show_file
  8.  
  9. MAX_RESULTS = 20 # per handler
  10.  
  11. try:
  12.     import beagle
  13. except:
  14.     # If this fails we complain about it in _check_requirements()
  15.     # so do nothing now
  16.     pass
  17.  
  18. def _show_start_beagle_dialog (dialog):
  19.     dialog = gtk.Dialog(_("Start Beagle Daemon?"), dialog,
  20.                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
  21.     
  22.     dialog.set_default_size (350, 150)
  23.     dialog.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)
  24.     dialog.add_button (_("Start Beagle Daemon"), gtk.RESPONSE_ACCEPT)
  25.     label = gtk.Label (_("The Beagle daemon does not appear to be running.\n You need to start it to use the Beagle Live handler."))
  26.     dialog.vbox.add (label)
  27.     label.show()
  28.  
  29.     response = dialog.run()
  30.     dialog.destroy()
  31.     
  32.     if response == gtk.RESPONSE_ACCEPT :
  33.         print "Starting Beagle Daemon."
  34.         if not spawn_async(["beagled"]):
  35.             print >> sys.stfderr, "Failed to start beagled. Perhaps the beagle daemon isn't installed?"    
  36.             warn = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, 
  37.                         type=gtk.MESSAGE_WARNING,
  38.                         buttons=gtk.BUTTONS_CLOSE,
  39.                         message_format=_("Failed to start Beagle"))
  40.             warn.format_secondary_text (_("Perhaps the beagle daemon isn't installed?"))
  41.             warn.run()
  42.             warn.destroy()
  43.  
  44. def _check_requirements():
  45.     # Check if we have python bindings for beagle
  46.     try:
  47.         import deskbar
  48.         import beagle
  49.     except Exception, e:
  50.         return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Could not load beagle, libbeagle has been compiled without python bindings:"+str(e), None)
  51.  
  52.     # Check if beagled is running        
  53.     if not beagle.beagle_util_daemon_is_running ():
  54.         if is_program_in_path("beagled"):
  55.             return (deskbar.Handler.HANDLER_HAS_REQUIREMENTS, "Beagle daemon is not running.", _show_start_beagle_dialog)
  56.         else:
  57.             return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Beagled could not be found in your $PATH. Unable to start the beagled daemon", None)
  58.     else:
  59.         return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
  60.     
  61. HANDLERS = {
  62.     "BeagleLiveHandler" : {
  63.         "name": _("Beagle Live"),
  64.         "description": _("Search all of your documents (using Beagle), as you type"),
  65.         # We must see how to detect properly beagle, for now it will fail on creating a new client
  66.         # when beagle is not available.
  67.         "requirements" : _check_requirements,
  68.         "version": VERSION,
  69.     }
  70. }
  71.  
  72. # The TYPES dict contains Beagle HitTypes as keys with
  73. # templates for the valid fields.
  74. #
  75. # A Hit Type consists of:
  76. #    "name"        : Template used to find a user-displayable name
  77. #    "action"    : Command to execute
  78. #    "icon"        : The *name* of the icon to be sued for this type. Set to None for no icon.
  79. #    "description"    : A short description. %(*)s may refer to *=name,uri,action or any field in "extra" (see below)
  80. #
  81. # Optionally:
  82. #    "extra"    : A dict containing key:template pairs to search for. You can use %(key)s in "description".
  83. #
  84. # Note:
  85. #  The templates are a tuple of strings which should be tested in order to retreive the beagle property
  86.  
  87. TYPES = {
  88.     "Contact"    : {
  89.         "name"    : ("fixme:FileAs",),
  90.         "action": "evolution %(uri)s",
  91.         "icon"    : "stock_contact",
  92.         "description": _("Edit contact %s") % "<b>%(name)s</b>",
  93.         "category": "people",
  94.         },
  95.     
  96.     "MailMessage"     : {
  97.         "name"    :("dc:title", "parent:dc:title"),
  98.         "action": "evolution %(uri)s",
  99.         "icon"    : "stock_mail",
  100.         "extra": {"sender":("fixme:from_name", "parent:fixme:from_name")},
  101.         "description": (_("From %s") % "<i>%(sender)s</i>" ) + "\n<b>%(name)s</b>",
  102.         "category": "emails",
  103.         },
  104.     "File"         : {
  105.         "name"    : ("beagle:ExactFilename",), 
  106.         "action": lambda d: url_show_file("file://"+d["uri"]),
  107.         "icon"    : "stock_new",
  108.         #translators: This is a file.
  109.         "description": _("Open %s") % "<b>%(name)s</b>",
  110.         "snippet": True,
  111.         "category": "files",
  112.         },
  113.     "FeedItem"    : {
  114.         "name"    : ("dc:title",),
  115.         "action": lambda d: url_show(d["identifier"]),
  116.         "icon"    : "stock_news",
  117.         "description": (_("News from %s") % "<i>%(publisher)s</i>" ) + "\n<b>%(name)s</b>",
  118.         "snippet": True,
  119.         "category": "news",
  120.         "extra": {"publisher":("dc:publisher",), "identifier": ("dc:identifier",)},
  121.         },
  122.     "Note"        : {
  123.         "name"    : ("dc:title",),
  124.         "action": "tomboy --open-note %(uri)s",
  125.         "icon"    :"stock_notes",
  126.         "description": _("Note: %s") % "<b>%(name)s</b>",
  127.         "snippet": True,
  128.         "category": "notes",
  129.         },
  130.     "IMLog"        : {
  131.         "name"    : ("fixme:speakingto",),
  132.         "extra" : {"client": ("fixme:client",)},
  133.         "action": "beagle-imlogviewer --client %(client)s --highlight-search '%(text)s' %(uri)s",
  134.         "icon"    : "im",
  135.         "description": _("With %s") % "<b>%(name)s</b>",
  136.         "snippet": True,
  137.         "category": "conversations",
  138.         },
  139.     "Calendar"    : {
  140.         "name"    : ("fixme:summary",),
  141.         "action": "evolution %(uri)s",
  142.         "icon"    : "stock_calendar",
  143.         "description": _("Calendar: %s") % "<b>%(name)s</b>",
  144.         "category": "documents",
  145.         },
  146.     "WebHistory": {
  147.         "name"    : ("dc:title",), # FIX-BEAGLE bug #330053, dc:title returns as None even though it _is_ set
  148.         "action": lambda d: url_show_file(d["uri"]),
  149.         "icon"    : "stock_bookmark",
  150.         "description": (_("Open History Item %s") % "<i>%(name)s</i>") + "\n%(escaped_uri)s",
  151.         "category": "web",
  152.         },
  153. }
  154.  
  155. # Append snippet text for snippet-enabled handlers
  156. for key, val in TYPES.items():
  157.     if "snippet" in val and val["snippet"]:
  158.         val["description"] += "%(snippet)s"
  159.         
  160. class BeagleLiveMatch (deskbar.Match.Match):
  161.     def __init__(self, handler, result=None, **args):
  162.         """
  163.         result: a dict containing:
  164.             "name" : a name sensible to display for this match
  165.             "uri": the uri of the match as provided by the beagled 'Uri: '-field
  166.             "type": One of the types listed in the TYPES dict
  167.  
  168.         -- and optionally extra fields as provided by the corresponding entry in TYPES.
  169.         Fx. "MailMessage". has an extra "sender" entry.
  170.         """
  171.         deskbar.Match.Match.__init__ (self, handler, name=result["name"], **args)
  172.         self.result = result
  173.  
  174.         # IM Log viewer take loca paths only        
  175.         action = TYPES[self.result["type"]]["action"]
  176.         if not callable(action) and action.startswith("beagle-imlogviewer"):
  177.             # Strip the uti descriptor, because imlogviewer takes a local path
  178.             self.result["uri"] = gnomevfs.get_local_path_from_uri(self.result["uri"])            
  179.         
  180.         # Load the correct icon
  181.         
  182.         #
  183.         # There is bug http://bugzilla.gnome.org/show_bug.cgi?id=319549
  184.         # which has been fixed and comitted, so we re-enable this snippet
  185.         #
  186.         
  187.         self._icon = None
  188.         if result["type"] == "File":
  189.             try:
  190.                 self._icon = deskbar.Utils.load_icon_for_file(result["uri"])
  191.             except Exception:
  192.                 pass
  193.         
  194.         if self._icon == None:
  195.             # Just use an icon from the ICON table
  196.             self._icon = handler.ICONS[result["type"]]
  197.         
  198.     def get_category (self):
  199.         try:
  200.             return TYPES[self.result["type"]]["category"]
  201.         except:
  202.             return "default"
  203.         
  204.     def get_name (self, text=None):
  205.         # We use the result dict itself to look up words
  206.         if text:
  207.             self.result["text"] = text
  208.             # Escape text since we use '%(text)s' as parameter
  209.             self.result["text"] = self.result["text"].replace("'", "\\'")
  210.         return self.result
  211.     
  212.     def get_verb(self):
  213.         # Fetch the "description" template from TYPES
  214.         return TYPES[self.result["type"]]["description"]
  215.         
  216.     def action(self, text=None):
  217.         # The call to get_name(text) ensures that we have
  218.         # the text field in the result dict
  219.         self.get_name(text)
  220.         
  221.         action = TYPES[self.result["type"]]["action"]
  222.         if callable(action):
  223.             action(self.result)
  224.         else:
  225.             # Retrieve the associated action
  226.             action = action % self.result
  227.             args = action.split(" ")
  228.  
  229.             print "BeagleLive spawning:", action, args
  230.             spawn_async(args)
  231.     
  232.     def get_hash(self, text=None):
  233.         if "uri" in self.result:
  234.             return self.result["uri"]
  235.  
  236. class SnippetContainer:
  237.     def __init__(self, hit):
  238.         self.hit = hit
  239.         self.snippet = None
  240.     
  241. class BeagleLiveHandler(deskbar.Handler.SignallingHandler):
  242.     def __init__(self):
  243.         deskbar.Handler.SignallingHandler.__init__(self, ("system-search", "best"))
  244.         self.counter = {}
  245.         self.snippets = {}
  246.         self.set_delay (500)
  247.         
  248.     def initialize (self):
  249.         self.beagle = beagle.Client()
  250.         self.ICONS = self.__load_icons()
  251.     
  252.     def __load_icons (self):
  253.         res = {}
  254.         for t in TYPES.iterkeys():
  255.             icon_file = TYPES[t]["icon"]
  256.             if not icon_file: continue
  257.             res[t] = deskbar.Utils.load_icon(icon_file)
  258.         return res
  259.         
  260.     def query (self, qstring):
  261.         beagle_query = beagle.Query()
  262.         beagle_query.add_text(qstring)
  263.         beagle_query.connect("hits-added", self.hits_added, qstring, MAX_RESULTS)
  264.         try:
  265.             self.beagle.send_request_async(beagle_query)
  266.         except:
  267.             return
  268.             
  269.         self.counter[qstring] = {}
  270.         
  271.     def _on_snippet_received(self, request, response, query, container, qstring, qmax):
  272.         container.snippet = response.get_snippet()
  273.         self._on_hit_added(query, container, qstring, qmax)
  274.     
  275.     def _on_snippet_closed(self, request, query, container, qstring, qmax):
  276.         if container.snippet == None:
  277.             self._on_hit_added(query, container, qstring, qmax)
  278.             
  279.         container.hit.unref()
  280.             
  281.     def _on_hit_added(self, query, hit, qstring, qmax):
  282.         fire_signal = False
  283.         snippet = None
  284.         if hit.__class__ == SnippetContainer:
  285.             hit, snippet = hit.hit, hit.snippet
  286.             fire_signal = True
  287.             
  288.         if not hit.get_type() in self.counter[qstring]:
  289.             self.counter[qstring][hit.get_type()] = 0
  290.  
  291.         if self.counter[qstring][hit.get_type()] >= qmax:
  292.             return
  293.             
  294.         hit_type = TYPES[hit.get_type()]
  295.         result = {
  296.             "uri":  hit.get_uri(),
  297.             "type": hit.get_type(),
  298.         }
  299.             
  300.         name = None
  301.         for prop in hit_type["name"]:
  302.             try:
  303.                 name = hit.get_properties(prop)[0] # get_property_one() would be cleaner, but this works around bug #330053
  304.             except:
  305.                 try:
  306.                     # Beagle < 0.2
  307.                     name = hit.get_property(prop)
  308.                 except:
  309.                     pass
  310.                     
  311.             if name != None:
  312.                 result["name"] = name
  313.                 break
  314.         
  315.         if name == None:
  316.             #translators: This is used for unknown values returned by beagle
  317.             #translators: for example unknown email sender, or unknown note title
  318.             result["name"] = _("?")
  319.             
  320.         if "extra" in hit_type:
  321.             for prop, keys in hit_type["extra"].items():
  322.                 val = None
  323.                 for key in keys:
  324.                     try:
  325.                         val = hit.get_properties(key)[0] # get_property_one() would be cleaner, but this works around bug #330053
  326.                     except:
  327.                         try:
  328.                             # Beagle < 0.2
  329.                             val = hit.get_property(key)
  330.                         except:
  331.                             pass
  332.                             
  333.                     if val != None:
  334.                         result[prop] = val
  335.                         break
  336.                     
  337.                 if val == None:
  338.                     #translators: This is used for unknown values returned by beagle
  339.                     #translators: for example unknown email sender, or unknown note title
  340.                     result[prop] = _("?")
  341.         
  342.         # Escape everything for display through pango markup, except filenames. Filenames are escaped in escaped_uri or 
  343.         # escaped_identifier
  344.         for key, val in result.items():
  345.             if key == "uri" or key == "identifier":
  346.                 result["escaped_"+key] = cgi.escape(val)
  347.             else:
  348.                 result[key] = cgi.escape(val)
  349.         
  350.         # Add the snippet, in escaped form if available
  351.         if snippet != None:
  352.             tmp = re.sub(r"<.*?>", "", snippet)
  353.             tmp = re.sub(r"</.*?>", "", tmp)
  354.             result["snippet"] = "\n<span foreground='grey' size='small'>%s</span>" % cgi.escape(tmp)
  355.         else:
  356.             result["snippet"] = ""
  357.             
  358.         self.counter[qstring][hit.get_type()] = self.counter[qstring][hit.get_type()] +1
  359.  
  360.         match = BeagleLiveMatch(self, result)
  361.         if fire_signal:
  362.             self.emit_query_ready(qstring, [match])
  363.         else:    
  364.             return match
  365.         
  366.     def hits_added(self, query, response, qstring, qmax):
  367.         hit_matches = []
  368.         for hit in response.get_hits():
  369.             if hit.get_type() not in TYPES:
  370.                 print 'WARNING: Beagle live seen an unknown type:', hit.get_type()
  371.                 continue
  372.  
  373.             if "snippet" in TYPES[hit.get_type()] and TYPES[hit.get_type()]["snippet"]:
  374.                 req = beagle.SnippetRequest()
  375.                 req.set_query(query)
  376.                 req.set_hit(hit)
  377.                 container = SnippetContainer(hit)
  378.                 hit.ref()
  379.                 req.connect('response', self._on_snippet_received, query, container, qstring, qmax)
  380.                 req.connect('closed', self._on_snippet_closed, query, container, qstring, qmax)
  381.                 self.beagle.send_request_async(req)
  382.                 continue
  383.                             
  384.             match = self._on_hit_added(query, hit, qstring, qmax)
  385.             if match != None:
  386.                 hit_matches.append(match)                
  387.             
  388.         self.emit_query_ready(qstring, hit_matches)
  389.