home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-xdg / xdg / Mime.py < prev    next >
Encoding:
Python Source  |  2005-05-26  |  11.0 KB  |  464 lines

  1. """
  2. This module is based on a rox module (LGPL):
  3.  
  4. http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
  5.  
  6. This module provides access to the shared MIME database.
  7.  
  8. types is a dictionary of all known MIME types, indexed by the type name, e.g.
  9. types['application/x-python']
  10.  
  11. Applications can install information about MIME types by storing an
  12. XML file as <MIME>/packages/<application>.xml and running the
  13. update-mime-database command, which is provided by the freedesktop.org
  14. shared mime database package.
  15.  
  16. See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
  17. information about the format of these files.
  18.  
  19. (based on version 0.13)
  20. """
  21.  
  22. import os
  23. import stat
  24. import fnmatch
  25.  
  26. import xdg.BaseDirectory
  27. import xdg.Locale
  28.  
  29. from xml.dom import Node, minidom, XML_NAMESPACE
  30.  
  31. FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
  32.  
  33. types = {}        # Maps MIME names to type objects
  34.  
  35. exts = None        # Maps extensions to types
  36. globs = None        # List of (glob, type) pairs
  37. literals = None        # Maps liternal names to types
  38. magic = None
  39.  
  40. def _get_node_data(node):
  41.     """Get text of XML node"""
  42.     return ''.join([n.nodeValue for n in node.childNodes]).strip()
  43.  
  44. def lookup(media, subtype = None):
  45.     "Get the MIMEtype object for this type, creating a new one if needed."
  46.     if subtype is None and '/' in media:
  47.         media, subtype = media.split('/', 1)
  48.     if (media, subtype) not in types:
  49.         types[(media, subtype)] = MIMEtype(media, subtype)
  50.     return types[(media, subtype)]
  51.  
  52. class MIMEtype:
  53.     """Type holding data about a MIME type"""
  54.     def __init__(self, media, subtype):
  55.         "Don't use this constructor directly; use mime.lookup() instead."
  56.         assert media and '/' not in media
  57.         assert subtype and '/' not in subtype
  58.         assert (media, subtype) not in types
  59.  
  60.         self.media = media
  61.         self.subtype = subtype
  62.         self._comment = None
  63.  
  64.     def _load(self):
  65.         "Loads comment for current language. Use get_comment() instead."
  66.         resource = os.path.join('mime', self.media, self.subtype + '.xml')
  67.         for path in xdg.BaseDirectory.load_data_paths(resource):
  68.             doc = minidom.parse(path)
  69.             if doc is None:
  70.                 continue
  71.             for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
  72.                 lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
  73.                 goodness = 1 + (lang in xdg.Locale.langs)
  74.                 if goodness > self._comment[0]:
  75.                     self._comment = (goodness, _get_node_data(comment))
  76.                 if goodness == 2: return
  77.  
  78.     # FIXME: add get_icon method
  79.     def get_comment(self):
  80.         """Returns comment for current language, loading it if needed."""
  81.         # Should we ever reload?
  82.         if self._comment is None:
  83.             self._comment = (0, str(self))
  84.             self._load()
  85.         return self._comment[1]
  86.  
  87.     def __str__(self):
  88.         return self.media + '/' + self.subtype
  89.  
  90.     def __repr__(self):
  91.         return '[%s: %s]' % (self, self._comment or '(comment not loaded)')
  92.  
  93. class MagicRule:
  94.     def __init__(self, f):
  95.         self.next=None
  96.         self.prev=None
  97.  
  98.         #print line
  99.         ind=''
  100.         while True:
  101.             c=f.read(1)
  102.             if c=='>':
  103.                 break
  104.             ind+=c
  105.         if not ind:
  106.             self.nest=0
  107.         else:
  108.             self.nest=int(ind)
  109.  
  110.         start=''
  111.         while True:
  112.             c=f.read(1)
  113.             if c=='=':
  114.                 break
  115.             start+=c
  116.         self.start=int(start)
  117.         
  118.         hb=f.read(1)
  119.         lb=f.read(1)
  120.         self.lenvalue=ord(lb)+(ord(hb)<<8)
  121.  
  122.         self.value=f.read(self.lenvalue)
  123.  
  124.         c=f.read(1)
  125.         if c=='&':
  126.             self.mask=f.read(self.lenvalue)
  127.             c=f.read(1)
  128.         else:
  129.             self.mask=None
  130.  
  131.         if c=='~':
  132.             w=''
  133.             while c!='+' and c!='\n':
  134.                 c=f.read(1)
  135.                 if c=='+' or c=='\n':
  136.                     break
  137.                 w+=c
  138.             
  139.             self.word=int(w)
  140.         else:
  141.             self.word=1
  142.  
  143.         if c=='+':
  144.             r=''
  145.             while c!='\n':
  146.                 c=f.read(1)
  147.                 if c=='\n':
  148.                     break
  149.                 r+=c
  150.             #print r
  151.             self.range=int(r)
  152.         else:
  153.             self.range=1
  154.  
  155.         if c!='\n':
  156.             raise 'Malformed MIME magic line'
  157.  
  158.     def getLength(self):
  159.         return self.start+self.lenvalue+self.range
  160.  
  161.     def appendRule(self, rule):
  162.         if self.nest<rule.nest:
  163.             self.next=rule
  164.             rule.prev=self
  165.  
  166.         elif self.prev:
  167.             self.prev.appendRule(rule)
  168.         
  169.     def match(self, buffer):
  170.         if self.match0(buffer):
  171.             if self.next:
  172.                 return self.next.match(buffer)
  173.             return True
  174.  
  175.     def match0(self, buffer):
  176.         l=len(buffer)
  177.         for o in range(self.range):
  178.             s=self.start+o
  179.             e=s+self.lenvalue
  180.             if l<e:
  181.                 return False
  182.             if self.mask:
  183.                 test=''
  184.                 for i in range(self.lenvalue):
  185.                     c=ord(buffer[s+i]) & ord(self.mask[i])
  186.                     test+=chr(c)
  187.             else:
  188.                 test=buffer[s:e]
  189.  
  190.             if test==self.value:
  191.                 return True
  192.  
  193.     def __repr__(self):
  194.         return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest,
  195.                                   self.start,
  196.                                   self.lenvalue,
  197.                                   `self.value`,
  198.                                   `self.mask`,
  199.                                   self.word,
  200.                                   self.range)
  201.  
  202. class MagicType:
  203.     def __init__(self, mtype):
  204.         self.mtype=mtype
  205.         self.top_rules=[]
  206.         self.last_rule=None
  207.  
  208.     def getLine(self, f):
  209.         nrule=MagicRule(f)
  210.  
  211.         if nrule.nest and self.last_rule:
  212.             self.last_rule.appendRule(nrule)
  213.         else:
  214.             self.top_rules.append(nrule)
  215.  
  216.         self.last_rule=nrule
  217.  
  218.         return nrule
  219.  
  220.     def match(self, buffer):
  221.         for rule in self.top_rules:
  222.             if rule.match(buffer):
  223.                 return self.mtype
  224.  
  225.     def __repr__(self):
  226.         return '<MagicType %s>' % self.mtype
  227.     
  228. class MagicDB:
  229.     def __init__(self):
  230.         self.types={}   # Indexed by priority, each entry is a list of type rules
  231.         self.maxlen=0
  232.  
  233.     def mergeFile(self, fname):
  234.         f=file(fname, 'r')
  235.         line=f.readline()
  236.         if line!='MIME-Magic\0\n':
  237.             raise 'Not a MIME magic file'
  238.  
  239.         while True:
  240.             shead=f.readline()
  241.             #print shead
  242.             if not shead:
  243.                 break
  244.             if shead[0]!='[' or shead[-2:]!=']\n':
  245.                 raise 'Malformed section heading'
  246.             pri, tname=shead[1:-2].split(':')
  247.             #print shead[1:-2]
  248.             pri=int(pri)
  249.             mtype=lookup(tname)
  250.  
  251.             try:
  252.                 ents=self.types[pri]
  253.             except:
  254.                 ents=[]
  255.                 self.types[pri]=ents
  256.  
  257.             magictype=MagicType(mtype)
  258.             #print tname
  259.  
  260.             #rline=f.readline()
  261.             c=f.read(1)
  262.             f.seek(-1, 1)
  263.             while c and c!='[':
  264.                 rule=magictype.getLine(f)
  265.                 #print rule
  266.                 if rule and rule.getLength()>self.maxlen:
  267.                     self.maxlen=rule.getLength()
  268.  
  269.                 c=f.read(1)
  270.                 f.seek(-1, 1)
  271.  
  272.             ents.append(magictype)
  273.             #self.types[pri]=ents
  274.             if not c:
  275.                 break
  276.  
  277.     def match(self, path, max_pri=100, min_pri=0):
  278.         try:
  279.             buf=file(path, 'r').read(self.maxlen)
  280.             pris=self.types.keys()
  281.             pris.sort(lambda a, b: -cmp(a, b))
  282.             for pri in pris:
  283.                 #print pri, max_pri, min_pri
  284.                 if pri>max_pri:
  285.                     continue
  286.                 if pri<min_pri:
  287.                     break
  288.                 for type in self.types[pri]:
  289.                     m=type.match(buf)
  290.                     if m:
  291.                         return m
  292.         except:
  293.             pass
  294.         
  295.         return None
  296.     
  297.     def __repr__(self):
  298.         return '<MagicDB %s>' % self.types
  299.             
  300.  
  301. # Some well-known types
  302. text = lookup('text', 'plain')
  303. inode_block = lookup('inode', 'blockdevice')
  304. inode_char = lookup('inode', 'chardevice')
  305. inode_dir = lookup('inode', 'directory')
  306. inode_fifo = lookup('inode', 'fifo')
  307. inode_socket = lookup('inode', 'socket')
  308. inode_symlink = lookup('inode', 'symlink')
  309. inode_door = lookup('inode', 'door')
  310. app_exe = lookup('application', 'executable')
  311.  
  312. _cache_uptodate = False
  313.  
  314. def _cache_database():
  315.     global exts, globs, literals, magic, _cache_uptodate
  316.  
  317.     _cache_uptodate = True
  318.  
  319.     exts = {}        # Maps extensions to types
  320.     globs = []        # List of (glob, type) pairs
  321.     literals = {}        # Maps liternal names to types
  322.     magic = MagicDB()
  323.  
  324.     def _import_glob_file(path):
  325.         """Loads name matching information from a MIME directory."""
  326.         for line in file(path):
  327.             if line.startswith('#'): continue
  328.             line = line[:-1]
  329.  
  330.             type_name, pattern = line.split(':', 1)
  331.             mtype = lookup(type_name)
  332.  
  333.             if pattern.startswith('*.'):
  334.                 rest = pattern[2:]
  335.                 if not ('*' in rest or '[' in rest or '?' in rest):
  336.                     exts[rest] = mtype
  337.                     continue
  338.             if '*' in pattern or '[' in pattern or '?' in pattern:
  339.                 globs.append((pattern, mtype))
  340.             else:
  341.                 literals[pattern] = mtype
  342.  
  343.     for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')):
  344.         _import_glob_file(path)
  345.     for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
  346.         magic.mergeFile(path)
  347.  
  348.     # Sort globs by length
  349.     globs.sort(lambda a, b: cmp(len(b[0]), len(a[0])))
  350.  
  351. def get_type_by_name(path):
  352.     """Returns type of file by its name, or None if not known"""
  353.     if not _cache_uptodate:
  354.         _cache_database()
  355.  
  356.     leaf = os.path.basename(path)
  357.     if leaf in literals:
  358.         return literals[leaf]
  359.  
  360.     lleaf = leaf.lower()
  361.     if lleaf in literals:
  362.         return literals[lleaf]
  363.  
  364.     ext = leaf
  365.     while 1:
  366.         p = ext.find('.')
  367.         if p < 0: break
  368.         ext = ext[p + 1:]
  369.         if ext in exts:
  370.             return exts[ext]
  371.     ext = lleaf
  372.     while 1:
  373.         p = ext.find('.')
  374.         if p < 0: break
  375.         ext = ext[p+1:]
  376.         if ext in exts:
  377.             return exts[ext]
  378.     for (glob, mime_type) in globs:
  379.         if fnmatch.fnmatch(leaf, glob):
  380.             return mime_type
  381.         if fnmatch.fnmatch(lleaf, glob):
  382.             return mime_type
  383.     return None
  384.  
  385. def get_type_by_contents(path, max_pri=100, min_pri=0):
  386.     """Returns type of file by its contents, or None if not known"""
  387.     if not _cache_uptodate:
  388.         _cache_database()
  389.  
  390.     return magic.match(path, max_pri, min_pri)
  391.  
  392. def get_type(path, follow=1, name_pri=100):
  393.     """Returns type of file indicated by path.
  394.     path     - pathname to check (need not exist)
  395.     follow   - when reading file, follow symbolic links
  396.     name_pri - Priority to do name matches.  100=override magic"""
  397.     if not _cache_uptodate:
  398.         _cache_database()
  399.     
  400.     try:
  401.         if follow:
  402.             st = os.stat(path)
  403.         else:
  404.             st = os.lstat(path)
  405.     except:
  406.         t = get_type_by_name(path)
  407.         return t or text
  408.  
  409.     if stat.S_ISREG(st.st_mode):
  410.         t = get_type_by_contents(path, min_pri=name_pri)
  411.         if not t: t = get_type_by_name(path)
  412.         if not t: t = get_type_by_contents(path, max_pri=name_pri)
  413.         if t is None:
  414.             if stat.S_IMODE(st.st_mode) & 0111:
  415.                 return app_exe
  416.             else:
  417.                 return text
  418.         return t
  419.     elif stat.S_ISDIR(st.st_mode): return inode_dir
  420.     elif stat.S_ISCHR(st.st_mode): return inode_char
  421.     elif stat.S_ISBLK(st.st_mode): return inode_block
  422.     elif stat.S_ISFIFO(st.st_mode): return inode_fifo
  423.     elif stat.S_ISLNK(st.st_mode): return inode_symlink
  424.     elif stat.S_ISSOCK(st.st_mode): return inode_socket
  425.     return inode_door
  426.  
  427. def install_mime_info(application, package_file):
  428.     """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
  429.     If package_file is None, install <app_dir>/<application>.xml.
  430.     If already installed, does nothing. May overwrite an existing
  431.     file with the same name (if the contents are different)"""
  432.     application += '.xml'
  433.  
  434.     new_data = file(package_file).read()
  435.  
  436.     # See if the file is already installed
  437.     package_dir = os.path.join('mime', 'packages')
  438.     resource = os.path.join(package_dir, application)
  439.     for x in xdg.BaseDirectory.load_data_paths(resource):
  440.         try:
  441.             old_data = file(x).read()
  442.         except:
  443.             continue
  444.         if old_data == new_data:
  445.             return    # Already installed
  446.  
  447.     global _cache_uptodate
  448.     _cache_uptodate = False
  449.  
  450.     # Not already installed; add a new copy
  451.     # Create the directory structure...
  452.     new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application)
  453.  
  454.     # Write the file...
  455.     file(new_file, 'w').write(new_data)
  456.  
  457.     # Update the database...
  458.     command = 'update-mime-database'
  459.     if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')):
  460.         os.unlink(new_file)
  461.         raise Exception("The '%s' command returned an error code!\n" \
  462.                   "Make sure you have the freedesktop.org shared MIME package:\n" \
  463.                   "http://standards.freedesktop.org/shared-mime-info/") % command
  464.