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 / mozilla.py < prev    next >
Encoding:
Python Source  |  2007-04-09  |  23.7 KB  |  752 lines

  1. import os, re, HTMLParser, base64, glob
  2. from os.path import join, expanduser, exists, basename
  3. from gettext import gettext as _
  4. from ConfigParser import RawConfigParser
  5. from xml.dom import minidom
  6.  
  7. from deskbar.defs import VERSION
  8. import gtk
  9. from deskbar.Watcher import FileWatcher, DirWatcher
  10. import deskbar, deskbar.Indexer, deskbar.Handler
  11.  
  12. # Check for presence of set to be compatible with python 2.3
  13. try:
  14.     set
  15. except NameError:
  16.     from sets import Set as set
  17.  
  18. from deskbar.BrowserMatch import is_preferred_browser
  19. from deskbar.BrowserMatch import BrowserSmartMatch, BrowserMatch
  20.  
  21. # Whether we will index firefox or mozilla bookmarks
  22. USING_FIREFOX = False
  23. if is_preferred_browser("firefox"):
  24.     USING_FIREFOX = True
  25.         
  26. # File returned here should be checked for existence
  27. def get_mozilla_home_file(needed_file):    
  28.     default_profile_dir = expanduser("~/.mozilla/default")
  29.     if exists(default_profile_dir):
  30.         for d in os.listdir(default_profile_dir):
  31.             return join(default_profile_dir, d, needed_file)
  32.     
  33.     return ""
  34.     
  35. def get_firefox_home_file(needed_file):
  36.     firefox_dir = expanduser("~/.mozilla/firefox/")
  37.     config = RawConfigParser({"Default" : 0})
  38.     config.read(expanduser(join(firefox_dir, "profiles.ini")))
  39.     path = None
  40.  
  41.     for section in config.sections():
  42.         if config.has_option(section, "Default") and config.get(section, "Default") == "1":
  43.             path = config.get (section, "Path")
  44.             break
  45.         elif path == None and config.has_option(section, "Path"):
  46.             path = config.get (section, "Path")
  47.         
  48.     if path == None:
  49.         return ""
  50.  
  51.     if path.startswith("/"):
  52.         return join(path, needed_file)
  53.  
  54.     return join(firefox_dir, path, needed_file)
  55.  
  56. # Whether we offer all of the browser's search engines, or only the primary
  57. # one (since by default Firefox seems to come with at least half a dozen)            
  58. GCONF_SHOW_ONLY_PRIMARY_KEY = deskbar.GCONF_DIR + "/mozilla/show_only_primary_search"
  59. SHOW_ONLY_PRIMARY = deskbar.GCONF_CLIENT.get_bool(GCONF_SHOW_ONLY_PRIMARY_KEY)
  60. if SHOW_ONLY_PRIMARY == None:
  61.     SHOW_ONLY_PRIMARY = False
  62. def _on_gconf_show_only_primary(value):
  63.     global SHOW_ONLY_PRIMARY
  64.     SHOW_ONLY_PRIMARY = value
  65. deskbar.GCONF_CLIENT.notify_add(GCONF_SHOW_ONLY_PRIMARY_KEY, lambda x, y, z, a: _on_gconf_show_only_primary(z.value.get_bool()))
  66.  
  67. # TODO re-load PRIMARY_SEARCH_ENGINE everytime it changes (which should happen
  68. # only rarely).  One (unavoidable) problem may be that firefox doesn't actually
  69. # save the change to disk until you quit firefox.
  70. PRIMARY_SEARCH_ENGINE = None
  71. try:
  72.     if USING_FIREFOX:
  73.         prefs_file = file(get_firefox_home_file("prefs.js"))
  74.     else:
  75.         # TODO - similar functionality for old-school mozilla (not firefox)
  76.         prefs_file = None
  77.     for line in prefs_file:
  78.         if line.startswith('user_pref("browser.search.selectedEngine", "'):
  79.             line = line.strip()
  80.             PRIMARY_SEARCH_ENGINE = line[len('user_pref("browser.search.selectedEngine", "'):-len('");')]
  81.             break
  82. except:
  83.     pass
  84.  
  85. def _on_handler_preferences(dialog):
  86.     def toggled_cb(sender, show_all_radio, show_primary_radio):
  87.         deskbar.GCONF_CLIENT.set_bool(GCONF_SHOW_ONLY_PRIMARY_KEY, show_primary_radio.get_active())
  88.         
  89.     def sync_ui(new_show_only_primary, show_all_radio, show_primary_radio):
  90.         show_all_radio.set_active(not new_show_only_primary)
  91.         show_primary_radio.set_active(new_show_only_primary)
  92.     
  93.     glade = gtk.glade.XML(os.path.join(deskbar.SHARED_DATA_DIR, "mozilla-search.glade"))
  94.     dialog = glade.get_widget("prefs-dialog")
  95.     show_all_radio = glade.get_widget("show_all_radio")
  96.     show_primary_radio = glade.get_widget("show_primary_radio")
  97.     
  98.     show_primary_radio.set_active(SHOW_ONLY_PRIMARY)
  99.     show_all_radio.set_active(not SHOW_ONLY_PRIMARY)
  100.     
  101.     show_all_radio.connect ("toggled", toggled_cb, show_all_radio, show_primary_radio)
  102.     show_primary_radio.connect ("toggled", toggled_cb, show_all_radio, show_primary_radio)
  103.     
  104.     notify_id = deskbar.GCONF_CLIENT.notify_add(GCONF_SHOW_ONLY_PRIMARY_KEY, lambda x, y, z, a: sync_ui(z.value.get_bool(), show_all_radio, show_primary_radio))
  105.     dialog.set_icon_name("deskbar-applet")
  106.     dialog.show_all()
  107.     dialog.run()
  108.     dialog.destroy()
  109.     deskbar.GCONF_CLIENT.notify_remove(notify_id)
  110.     
  111. def _check_requirements_bookmarks():
  112. #    if deskbar.UNINSTALLED_DESKBAR:
  113. #        return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
  114.     
  115.     if is_preferred_browser("firefox") or is_preferred_browser("mozilla"):
  116.         return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
  117.     else:
  118.         return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Mozilla/Firefox is not your preferred browser, not using it.", None)
  119.         
  120. def _check_requirements_search():
  121.     if is_preferred_browser("firefox"):
  122.         return (deskbar.Handler.HANDLER_IS_CONFIGURABLE, _("You can customize which search engines are offered."), _on_handler_preferences)
  123.     elif is_preferred_browser("mozilla"):
  124.         # TODO - similar functionality for old-school mozilla (not firefox)
  125.         return (deskbar.Handler.HANDLER_IS_HAPPY, None, None)
  126.     else:
  127.         return (deskbar.Handler.HANDLER_IS_NOT_APPLICABLE, "Mozilla/Firefox is not your preferred browser, not using it.", None)
  128.         
  129. HANDLERS = {
  130.     "MozillaBookmarksHandler" : {
  131.         "name": _("Web Bookmarks"),
  132.         "description": _("Open your web bookmarks by name"),
  133.         "requirements": _check_requirements_bookmarks,
  134.         "version": VERSION,
  135.     },
  136.     "MozillaSearchHandler" : {
  137.         "name": _("Web Searches"),
  138.         "description": _("Search the web via your browser's search settings"),
  139.         "requirements": _check_requirements_search,
  140.         "version": VERSION,
  141.     },
  142.     "MozillaHistoryHandler" : {
  143.         "name": _("Web History"),
  144.         "description": _("Open your web history by name"),
  145.         "requirements": _check_requirements_bookmarks,
  146.         "version": VERSION,
  147.     }
  148. }
  149.  
  150. class MozillaBookmarksHandler(deskbar.Handler.Handler):
  151.     def __init__(self):
  152.         deskbar.Handler.Handler.__init__(self, "stock_bookmark")
  153.         self._bookmarks = None
  154.     
  155.     def initialize(self):
  156.         if not hasattr(self, 'watcher'):
  157.             self.watcher = FileWatcher()
  158.             self.watcher.connect('changed', lambda watcher, f: self._parse_bookmarks())
  159.         
  160.         # We do some gym to get the effectively parsed files
  161.         parsed_file = self._parse_bookmarks()
  162.         if parsed_file != None:
  163.             self.watcher.add(parsed_file)
  164.         
  165.     def _parse_bookmarks(self):
  166.         self._bookmarks, parsed_file, self._shortcuts_to_smart_bookmarks_map = MozillaBookmarksParser(self).get_indexer()
  167.         return parsed_file
  168.     
  169.     def stop(self):
  170.         self.watcher.remove_all()
  171.         
  172.     def query(self, query):
  173.         # First, check the smart bookmarks, or "keywords", where
  174.         # "wp Foo" takes you to the wikipedia entry for Foo.
  175.         x = self.query_smart_bookmarks(query, deskbar.DEFAULT_RESULTS_PER_HANDLER)
  176.         if x != None:
  177.             return x
  178.         else:
  179.             # If none of the smart bookmarks matched as a prefix,
  180.             # then we'll just look up all bookmarks.
  181.             return self._bookmarks.look_up(query)[:deskbar.DEFAULT_RESULTS_PER_HANDLER]
  182.     
  183.     def query_smart_bookmarks(self, query, max):
  184.         # if one of the smart bookmarks' shortcuts matches as a prefix,
  185.         # then only return that bookmark
  186.         x = query.find(" ")
  187.         if x != -1:
  188.             prefix = query[:x]
  189.             try:
  190.                 b = self._shortcuts_to_smart_bookmarks_map[prefix]
  191.                 text = query[x+1:]
  192.                 return [BrowserSmartMatch(b.get_handler(), b.name, b.url, prefix, b, icon=b.icon)]
  193.             except KeyError:
  194.                 # Probably from the b = ... line.  Getting here
  195.                 # means that there is no such shortcut.
  196.                 pass
  197.         return None
  198.  
  199. class MozillaSearchHandler(deskbar.Handler.Handler):
  200.     def __init__(self):
  201.         deskbar.Handler.Handler.__init__(self, "stock_bookmark")
  202.         self._smart_bookmarks = None
  203.     
  204.     def initialize(self):
  205.         smart_dirs = None
  206.         if USING_FIREFOX:
  207.             smart_dirs = [
  208.                 get_firefox_home_file("searchplugins"),
  209.                 get_firefox_home_file("search"),
  210.                 expanduser("~/.mozilla/searchplugins"),
  211.                 "/usr/lib/firefox/searchplugins",
  212.                 "/usr/local/lib/firefox/searchplugins",
  213.                 "/usr/lib/mozilla-firefox/searchplugins",
  214.                 "/usr/local/lib/mozilla-firefox/searchplugins"]
  215.         else:
  216.             smart_dirs = [
  217.                 get_mozilla_home_file("search"),
  218.                 expanduser("~/.mozilla/searchplugins"),
  219.                 "/usr/lib/mozilla/searchplugins",
  220.                 "/usr/local/lib/mozilla/searchplugins"]
  221.         
  222.         if not hasattr(self, 'watcher'):
  223.             self.watcher = DirWatcher()
  224.             self.watcher.connect('changed', lambda watcher, f: self._parse_search_engines(smart_dirs))
  225.         
  226.         self.watcher.add(smart_dirs)
  227.         self._parse_search_engines(smart_dirs)
  228.         
  229.     def _parse_search_engines(self, smart_dirs):
  230.         self._smart_bookmarks = MozillaSmartBookmarksDirParser(self, smart_dirs).get_smart_bookmarks()
  231.  
  232.     def stop(self):
  233.         self.watcher.remove_all()
  234.         
  235.     def query(self, query):
  236.         if SHOW_ONLY_PRIMARY and PRIMARY_SEARCH_ENGINE != None:
  237.             for s in self._smart_bookmarks:
  238.                 if s.name == PRIMARY_SEARCH_ENGINE:
  239.                     return [s]
  240.             return self._smart_bookmarks
  241.         else:
  242.             return self._smart_bookmarks
  243.         
  244. class MozillaBookmarksParser(HTMLParser.HTMLParser):
  245.     def __init__(self, handler):
  246.         HTMLParser.HTMLParser.__init__(self)
  247.         
  248.         self.handler = handler
  249.         
  250.         self.chars = ""
  251.         self.href = None
  252.         self.icon_data = None
  253.         self.bookmarks = set()
  254.         self._shortcuts_to_smart_bookmarks_map = {}
  255.         
  256.         self._indexer = deskbar.Indexer.Indexer()
  257.         
  258.         try:
  259.             if USING_FIREFOX:
  260.                 self.indexed_file = self._index_firefox()
  261.             else:
  262.                 self.indexed_file = self._index_mozilla()
  263.             self.close()
  264.         except Exception, e:
  265.             print 'Warning:Could not index Firefox bookmarks:', e
  266.         
  267.     def get_indexer(self):
  268.         """
  269.         Returns a completed indexer with the contents of bookmark file,
  270.         the name of the indexed file, and a map from shortcuts (or
  271.         prefixes) to smart bookmarks - those bookmarks with %s in the
  272.         URL.
  273.         """
  274.         return (self._indexer, self.indexed_file, self._shortcuts_to_smart_bookmarks_map)
  275.         
  276.     def _index_mozilla(self):
  277.         try:
  278.             bookmarks_file = get_mozilla_home_file("bookmarks.html")
  279.             if exists(bookmarks_file):
  280.                 self.feed(file(bookmarks_file).read())
  281.                 return bookmarks_file
  282.         except Exception, msg:
  283.             print 'Error retreiving Mozilla Bookmarks:', msg
  284.         
  285.     def _index_firefox(self):
  286.         try:
  287.             bookmarks_file = get_firefox_home_file("bookmarks.html")
  288.             if exists(bookmarks_file):
  289.                 self.feed(file(bookmarks_file).read())
  290.                 return bookmarks_file
  291.         except Exception, msg:
  292.             print 'Error retreiving Mozilla Bookmarks:', msg
  293.     
  294.     def handle_starttag(self, tag, attrs):
  295.         tag = tag.lower()
  296.         if tag == "a":
  297.             self.chars = ""
  298.             self.shortcuturl = None
  299.             for tag, value in attrs:
  300.                 if tag.lower() == 'href':
  301.                     self.href = value
  302.                 if tag.lower() == 'icon' and value != "data:" and value.startswith("data:"):
  303.                     self.icon_data = value
  304.                 if tag.lower() == 'shortcuturl':
  305.                     self.shortcuturl = value
  306.  
  307.     def handle_endtag(self, tag):
  308.         tag = tag.lower()
  309.         if tag == "a":
  310.             if self.href == None or self.href.startswith("javascript:"):
  311.                 return
  312.             
  313.             pixbuf = None
  314.             if self.icon_data != None:
  315.                 try:
  316.                     # data:text/html;base64 should be the Header
  317.                     header, content = self.icon_data.split(",", 2)
  318.                     loader = gtk.gdk.PixbufLoader()
  319.                     loader.set_size(deskbar.ICON_HEIGHT, deskbar.ICON_HEIGHT)
  320.                     try:
  321.                         # Python 2.4
  322.                         loader.write(base64.b64decode(content))
  323.                     except AttributeError:
  324.                         # Python 2.3 and earlier
  325.                         loader.write(base64.decodestring(content))
  326.                     loader.close()
  327.                     pixbuf = loader.get_pixbuf()
  328.                 except Exception, msg:
  329.                     print 'Error:mozilla.py:handle_endtag:', msg
  330.                 # Reset icon data for the following icon
  331.                 self.icon_data = None
  332.                 
  333.             bookmark = BrowserMatch(self.handler, self.chars, self.href, icon=pixbuf)
  334.             if self.shortcuturl != None:
  335.                 bookmark = BrowserSmartMatch(self.handler, self.chars, self.href, self.shortcuturl, bookmark, icon=pixbuf)
  336.                 self._shortcuts_to_smart_bookmarks_map[self.shortcuturl] = bookmark
  337.             else:
  338.                 self._indexer.add("%s %s" % (self.chars, self.href), bookmark)
  339.  
  340.     def handle_data(self, chars):
  341.         self.chars = self.chars + chars
  342.  
  343. class Firefox2SearchEngineParser :
  344.     def __init__ (self, filename):
  345.         self.filename = filename
  346.         
  347.         self._infos = {
  348.             "name" : "",
  349.             "action" : "",
  350.             "description" : "",
  351.             "url" : ""
  352.         }
  353.         
  354.         self._namespace = None
  355.         
  356.     def get_infos (self):
  357.         return self._infos
  358.     
  359.     def parse (self):
  360.         xml = minidom.parse(self.filename)
  361.         
  362.         self._detect_namespace(xml)
  363.         
  364.         self._parse_name (xml)
  365.         self._parse_description (xml)
  366.         self._parse_action_and_url (xml)
  367.         
  368.         try:
  369.             self._parse_image (xml)
  370.         except Exception, msg:
  371.             print "Error parsing icon for %s\n%s" % (self.filename,msg)
  372.     
  373.     def _detect_namespace (self, xml):
  374.         # Manually added search engines use the "os" namespace
  375.         # for some reason.
  376.         try:
  377.             plugin = xml.getElementsByTagName ("SearchPlugin")[0]
  378.             ns = plugin.getAttribute("xmlns:os")
  379.             if ns == "":
  380.                 return
  381.             self._namespace = "os"            
  382.         except:
  383.             pass
  384.             
  385.     def _ns_convert (self, tagname):
  386.         # Convert the tag to the configured xml namespace
  387.         if self._namespace is not None:
  388.             return self._namespace + ":" + tagname
  389.         else:
  390.             return tagname
  391.     
  392.     def _parse_name (self, xml):
  393.         for node in xml.getElementsByTagName (self._ns_convert("ShortName"))[0].childNodes:
  394.             self._infos ["name"] += node.data
  395.     
  396.     def _parse_description (self, xml):
  397.         for node in xml.getElementsByTagName (self._ns_convert("Description"))[0].childNodes:
  398.             self._infos ["description"] += node.data
  399.             
  400.     def _parse_action_and_url (self, xml):
  401.         
  402.         # Some search engines have multiple Url tags
  403.         # - fx. to a suggest-service, so we have to
  404.         # detect the correct one. It will be the one
  405.         # with type text/html.
  406.         url_node = None
  407.         for node in xml.getElementsByTagName (self._ns_convert("Url")):
  408.             if node.getAttribute("type") == "text/html":
  409.                 url_node = node
  410.                 break 
  411.         
  412.         if url_node is None:
  413.             raise ParseException ("No Url tag of type text/html in %s" % self.filename)
  414.         
  415.         self._infos ["url"] = url_node.getAttribute ("template")
  416.         self._infos ["action"] = self._infos["url"]
  417.         
  418.         # Append search paramters to the action url
  419.         params = ""
  420.         for param in url_node.getElementsByTagName(self._ns_convert("Param")):
  421.             key = param.getAttribute ("name")
  422.             value = param.getAttribute ("value")            
  423.             params += "&%s=%s" % (key,value)
  424.         
  425.         # Cut away leading & in param string
  426.         params = params[1:]
  427.         
  428.         if params != "":
  429.             self._infos["action"] += "?" + params
  430.         
  431.         # Escape the "{searchTerms}" parameter and take care of spelling variations
  432.         self._infos["action"] = self._infos["action"].replace("{searchTerms}", "%s")
  433.         self._infos["action"] = self._infos["action"].replace("{SearchTerms}", "%s")
  434.         
  435.     
  436.     def _parse_image (self, xml):
  437.         # The Image tag contains a base64 encoded image 
  438.         try:
  439.             # Some custom search engines might now contain a favicon
  440.             img_tag = xml.getElementsByTagName(self._ns_convert("Image"))[0]
  441.         except:
  442.             return
  443.         
  444.         loader = gtk.gdk.PixbufLoader()
  445.         loader.set_size(deskbar.ICON_HEIGHT, deskbar.ICON_HEIGHT)
  446.         
  447.         content = ""
  448.         for data in img_tag.childNodes:
  449.             content += data.data
  450.         
  451.         # Strip header data before decoding the base64 image
  452.         header = "data:image/x-icon;base64,"
  453.         content = content[content.index(header) + len(header):]
  454.         
  455.         try:
  456.             # Python 2.4
  457.             loader.write(base64.b64decode(content))
  458.         except AttributeError:
  459.             # Python 2.3 and earlier
  460.             loader.write(base64.decodestring(content))
  461.         
  462.         loader.close()
  463.         pixbuf = loader.get_pixbuf()
  464.         self._infos["pixbuf"] = pixbuf
  465.                 
  466. class MozillaSmartBookmarksParser:
  467.     def __init__(self, f):
  468.         """
  469.         Init the parse, no exception here
  470.         """
  471.         self.f = f
  472.         self.infos = {
  473.             "search": {},
  474.             "input": {},
  475.         }
  476.     
  477.     def get_infos(self):
  478.         infos = {}
  479.         
  480.         args = "?"
  481.         for key, arg in self.infos["input"].items():
  482.             args += '%s=%s&' % (key, arg)
  483.         
  484.         if args.endswith("&"):
  485.             args = args[:-1]
  486.         
  487.         infos["name"] = self.infos["search"]["name"]
  488.         infos["description"] = self.infos["search"]["description"]
  489.  
  490.         # FIXME: If we don't have a real fallback url, doing this will most probably
  491.         # result in some error. Ideally, we should use gnomevfs to extract the
  492.         # simple hostname, for example: https://www.amazon.com/obidos/blah/q=%s&ie=7753829
  493.         # should be trimmed to https://www.amazon.com
  494.         if not "url" in self.infos["search"]:
  495.             infos["url"] = self.infos["search"]["action"]
  496.         else:
  497.             infos["url"] = self.infos["search"]["url"]
  498.             
  499.         infos["action"] = self.infos["search"]["action"] + args
  500.         
  501.         img = self._find_icon ()
  502.         if img is not None:
  503.             infos["icon"] = img
  504.         
  505.         return infos
  506.     
  507.     def parse(self):
  508.         """
  509.         """
  510.         tokenizer = Tokenizer(self.f)
  511.         n = state = None
  512.         
  513.         # We load the two first tokens, whch should be ["<", "search"]
  514.         tokens = [tokenizer.get_next_token(), tokenizer.get_next_token()]
  515.         while tokens[0] != None:
  516.             # Retreive the next state, the number of tokens to read next, and any discarded tokens
  517.             # by the handler, which should be handled on next iteration
  518.             state, n, rest = self._handle_token(state, tokens)
  519.             # If n == None, we finished parsing
  520.             if n == None:
  521.                 break
  522.             
  523.             # Read the requested number of tokens, but count the ones that were rejected first
  524.             tokens = rest
  525.             for i in range(n - len(rest)):
  526.                 tokens.append(tokenizer.get_next_token())
  527.     
  528.     def _find_icon (self):
  529.         try:
  530.             parent_dir = self.f[:self.f.rindex("/")]
  531.             return [img for img in glob.glob(join(parent_dir, '%s.*' % self.f[:-4])) if not img.endswith(".src")][0]
  532.         except Exception, msg:
  533.             print "WARNING: Error detecting icon for smart bookmark:%s\n%s" % (self.f,msg)
  534.             return None
  535.     
  536.     def _handle_token(self, state, tokens):
  537.         if state == None and (tokens == ["<", "search"] or tokens == ["<", "SEARCH"]):
  538.             return "search", 3, []
  539.         elif state == None:
  540.             raise ParseException("File %s does not begin with <search" % self.f)
  541.         
  542.         # Read key=value pairs and store them in the infos["search"] dict
  543.         if state == "search" and tokens[1] == "=":
  544.             self.infos["search"][tokens[0]] = tokens[2]
  545.             return "search", 3, []
  546.         
  547.         # We reached the end of <Search tag, now in theory come the <input> tags
  548.         if state == "search" or state == "anotherinput" and tokens == [">", "<", "input"]:
  549.             return "input", 6, []
  550.         elif state == "search":
  551.             raise ParseException("Expecting <input after <search section in file %s" % self.f)
  552.         
  553.         # This parses the <input fields, taking each time 6 tokens
  554.         # First triplet is name=value, second triplet is value=val
  555.         # Special case for lone "user" second triplets, and store that in infos["input"] dict
  556.         if state == "input" and tokens[1] == "=":
  557.             if tokens[3] == "user":
  558.                 self.infos["input"][tokens[2]] = "%s"
  559.                 if tokens[4] != "=":
  560.                     return "anotherinput", 3, tokens[4:]
  561.             else:
  562.                 self.infos["input"][tokens[2]] = tokens[5]
  563.             return "anotherinput", 3, []
  564.         
  565.         # Here we stop processing, cause we are no longer in <input sections, and what comes after isn't interesting
  566.         return None, None, None
  567.  
  568. class TokenException(Exception):
  569.     def __init__ (self, msg): Exception.__init__(self, msg)
  570. class ParseException(Exception):
  571.     def __init__ (self, msg): Exception.__init__(self, msg)
  572.     
  573. class Tokenizer:
  574.     def __init__(self, f):
  575.         """
  576.         Init the tokenizer on the given file, may throw an exception.
  577.         """
  578.         self.i = 0
  579.         # Read the file, coud be streamed as well
  580.         self.f = f
  581.         self.data = file(f).read()
  582.         
  583.         self.state = "linestart"
  584.         
  585.     def get_next_token(self):
  586.         """
  587.         Returns the next token in the file.
  588.         A token is one of:
  589.         < = > name
  590.         
  591.         Returns None when the end of file is reached and beyond
  592.         """
  593.         while self.i < len(self.data):
  594.             char = self.data[self.i]
  595.             # Skip leading spaces (which are really trailing spaces after a token
  596.             # Newline is important so special case it
  597.             if char.isspace() and not char == "\n":
  598.                 self.i += 1
  599.                 continue
  600.             
  601.             if self.state == "linestart" and char == "#":
  602.                 # Eat all the comment line
  603.                 while self.i < len(self.data) and self.data[self.i] != "\n":
  604.                     self.i += 1
  605.                 self.i += 1 #position on first char after newline
  606.                 continue
  607.             
  608.             # At this point, we can only have a token, read it, and return it
  609.             # The method updates the self.i to point to the new char to read next
  610.             next_token = self.read_token()
  611.             # Wait ! We got a newline here, so back to start again..
  612.             if next_token == "\n":
  613.                 self.state = "linestart"
  614.                 continue
  615.                 
  616.             return next_token
  617.     
  618.     def read_token(self):
  619.         """
  620.         Return the token, self.i must point to the first char in this token.
  621.         self.i is updated to point just after the returned token
  622.         Returned token is one of:
  623.         < = > \n name
  624.         """
  625.         char = self.data[self.i]
  626.         
  627.         if char == "<" or char == "=" or char == ">" or char == "\n":
  628.             self.i += 1
  629.             return char
  630.         
  631.         elif char == "\"":
  632.             # Here we assume proper quoting
  633.             closing = self.data[self.i+1:].find("\"")
  634.             if closing == -1:
  635.                 raise TokenException("Couldn't find a proper closing quote in %s" % self.f)
  636.             
  637.             token = self.data[self.i+1:self.i+1+closing]
  638.             self.i += closing+2 #Next char is just *after* the closing "
  639.             return token
  640.         
  641.         else:
  642.             # Token can just be a string now..
  643.             token = ""
  644.             # We eat all chars until one of "=<>" or whitespace is found
  645.             while self.i < len(self.data) and not self.data[self.i].isspace() and not self.data[self.i] in "=><":
  646.                 token += self.data[self.i]
  647.                 self.i += 1
  648.                 
  649.             return token
  650.             
  651. class MozillaSmartBookmarksDirParser:
  652.     def __init__(self, handler, dirs):
  653.         self._smart_bookmarks = []
  654.         
  655.         # Avoid getting duplicate search engines
  656.         bookmark_names = []
  657.         
  658.         # Full path to detected bookmark file
  659.         found_bookmarks = []
  660.         
  661.         for bookmarks_dir in dirs:
  662.             if not exists(bookmarks_dir):
  663.                 continue
  664.             
  665.             # Detect Firefox <= 1.5 search engines  
  666.             for f in glob.glob(join(bookmarks_dir, '*.src')):
  667.                 # Check if we already parsed the file
  668.                 bmname = basename(f)
  669.                 if bmname in bookmark_names:
  670.                     continue
  671.                 else:
  672.                     found_bookmarks.append(f)
  673.             
  674.             # Detect Firefox >= 2.0 search engines
  675.             for f in glob.glob(join(bookmarks_dir, '*.xml')):
  676.                 # Check if we already parsed the file
  677.                 bmname = basename(f)
  678.                 if bmname in bookmark_names:
  679.                     continue
  680.                 else:
  681.                     found_bookmarks.append(f)
  682.             
  683.             
  684.         for f in found_bookmarks:
  685.             img = None
  686.             if f.endswith (".xml"):
  687.                 # Firefox >= 2.0 format
  688.                 parser = Firefox2SearchEngineParser (f)
  689.             else:
  690.                 # f ends with ".src" and is in Firefox <= 1.5 format 
  691.                 parser = MozillaSmartBookmarksParser(f)
  692.                             
  693.             try:
  694.                 parser.parse()
  695.                 infos = parser.get_infos()
  696.                 
  697.                 if infos.has_key("pixbuf"):
  698.                     bookmark = BrowserMatch(handler, infos["name"], infos["url"], pixbuf=infos["pixbuf"])
  699.                     bookmark = BrowserSmartMatch(handler, infos["name"], infos["action"], pixbuf=infos["pixbuf"], bookmark=bookmark)
  700.                 elif infos.has_key ("icon"):
  701.                     bookmark = BrowserMatch(handler, infos["name"], infos["url"], icon=infos["icon"])
  702.                     bookmark = BrowserSmartMatch(handler, infos["name"], infos["action"], icon=infos["icon"], bookmark=bookmark)
  703.                 else:
  704.                     bookmark = BrowserMatch(handler, infos["name"], infos["url"])
  705.                     bookmark = BrowserSmartMatch(handler, infos["name"], infos["action"], bookmark=bookmark)
  706.                     
  707.                 self._smart_bookmarks.append(bookmark)
  708.                 
  709.             except Exception, msg:
  710.                 print 'Error:MozillaSmartBookmarksDirParser:cannot parse smart bookmark: %s\n%s' % (f,msg)
  711.                     
  712.     
  713.     def get_smart_bookmarks(self):
  714.         """
  715.         Return a list of MozillaSmartMatch instances representing smart bookmarks
  716.         """
  717.         return self._smart_bookmarks
  718.  
  719. MOZILLA_HISTORY_REGEX = re.compile("\=http[0-9a-zA-Z\-\&\%\=\?\:\/\.]*\)")
  720. class MozillaHistoryHandler(deskbar.Handler.Handler):
  721.     def __init__(self):
  722.         deskbar.Handler.Handler.__init__(self, "epiphany-history.png")
  723.         self._history = None
  724.     
  725.     def initialize(self):
  726.         self._indexer = deskbar.Indexer.Indexer()
  727.         self._history = self._parse_history()
  728.         for history_url in self._history:
  729.             history_wo_http = history_url[history_url.find('//')+2:]
  730.             if history_wo_http.find('www.') == -1:
  731.                 history_wo_www = history_wo_http
  732.             else:
  733.                 history_wo_www = history_wo_http[history_wo_http.find('www.')+4:]
  734.             self._indexer.add("%s %s %s" % (history_wo_www, history_wo_http, history_url), BrowserMatch(self, history_wo_www, history_url, True))
  735.         
  736.     def _parse_history(self):
  737.         if USING_FIREFOX:
  738.             historydat = get_firefox_home_file("history.dat")
  739.         else:
  740.             historydat = get_mozilla_home_file("history.dat")
  741.         try:
  742.             historycontents = file(historydat).read()
  743.             historycontents = re.findall(MOZILLA_HISTORY_REGEX, historycontents)
  744.             historycontents = [x[1:-1] for x in historycontents]
  745.             return historycontents
  746.         except:
  747.             return ""
  748.     
  749.     def query(self, query):
  750.         return self._indexer.look_up(query)[:deskbar.DEFAULT_RESULTS_PER_HANDLER]
  751.  
  752.