home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / iconcache.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  10.7 KB  |  332 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. import item
  19. import os
  20. import threading
  21. import httpclient
  22. from fasttypes import LinkedList
  23. from eventloop import asIdle, addIdle, addTimeout
  24. from download_utils import nextFreeFilename, getFileURLPath
  25. from util import unicodify, call_command
  26. from platformutils import unicodeToFilename
  27. import config
  28. import prefs
  29. import time
  30. import views
  31. import random
  32. import imageresize
  33.  
  34. RUNNING_MAX = 3
  35.     
  36. def clearOrphans():
  37.     knownIcons = set()
  38.     for item in views.items:
  39.         if item.iconCache and item.iconCache.filename:
  40.             knownIcons.add(os.path.normcase(item.iconCache.filename))
  41.             for resized in item.iconCache.resized_filenames.values():
  42.                 knownIcons.add(os.path.normcase(resized))
  43.     for feed in views.feeds:
  44.         if feed.iconCache and feed.iconCache.filename:
  45.             knownIcons.add(os.path.normcase(feed.iconCache.filename))
  46.             for resized in feed.iconCache.resized_filenames.values():
  47.                 knownIcons.add(os.path.normcase(resized))
  48.     cachedir = config.get(prefs.ICON_CACHE_DIRECTORY)
  49.     if os.path.isdir(cachedir):
  50.         existingFiles = [os.path.normcase(os.path.join(cachedir, f)) 
  51.                 for f in os.listdir(cachedir)]
  52.         for filename in existingFiles:
  53.             if (os.path.exists(filename) and
  54.                 os.path.basename(filename)[0] != '.' and
  55.                 os.path.basename(filename) != 'extracted' and
  56.                 not filename in knownIcons):
  57.                 try:
  58.                     os.remove (filename)
  59.                 except OSError:
  60.                     pass
  61.  
  62. class IconCacheUpdater:
  63.     def __init__ (self):
  64.         self.idle = LinkedList()
  65.         self.vital = LinkedList()
  66.         self.runningCount = 0
  67.         self.inShutdown = False
  68.  
  69.     @asIdle
  70.     def requestUpdate (self, item, is_vital = False):
  71.         if is_vital:
  72.             item.dbItem.confirmDBThread()
  73.             if item.filename and os.access (item.filename, os.R_OK):
  74.                 is_vital = False
  75.         if self.runningCount < RUNNING_MAX:
  76.             addIdle (item.requestIcon, "Icon Request")
  77.             self.runningCount += 1
  78.         else:
  79.             if is_vital:
  80.                 self.vital.prepend(item)
  81.             else:
  82.                 self.idle.prepend(item)
  83.  
  84.     def updateFinished (self):
  85.         if self.inShutdown:
  86.             self.runningCount -= 1
  87.             return
  88.  
  89.         if len (self.vital) > 0:
  90.             item = self.vital.pop()
  91.         elif len (self.idle) > 0:
  92.             item = self.idle.pop()
  93.         else:
  94.             self.runningCount -= 1
  95.             return
  96.         
  97.         addIdle (item.requestIcon, "Icon Request")
  98.  
  99.     @asIdle
  100.     def clearVital (self):
  101.         self.vital = LinkedList()
  102.  
  103.     @asIdle
  104.     def shutdown (self):
  105.         self.inShutdown = True
  106.  
  107. iconCacheUpdater = IconCacheUpdater()
  108. class IconCache:
  109.     def __init__ (self, dbItem, is_vital = False):
  110.         self.etag = None
  111.         self.modified = None
  112.         self.filename = None
  113.         self.resized_filenames = {}
  114.         self.url = None
  115.  
  116.         self.updated = False
  117.         self.updating = False
  118.         self.needsUpdate = False
  119.         self.dbItem = dbItem
  120.         self.removed = False
  121.  
  122.         self.requestUpdate (is_vital=is_vital)
  123.  
  124.     def remove (self):
  125.         self.removed = True
  126.         self._removeFile(self.filename)
  127.         imageresize.removeResizedFiles(self.resized_filenames)
  128.  
  129.     def _removeFile(self, filename):
  130.         try:
  131.             os.remove (filename)
  132.         except:
  133.             pass
  134.  
  135.     def errorCallback(self, url, error = None):
  136.         self.dbItem.confirmDBThread()
  137.  
  138.         if self.removed:
  139.             iconCacheUpdater.updateFinished()
  140.             return
  141.  
  142.         # Don't clear the cache on an error.
  143.         if self.url != url:
  144.             self.url = url
  145.             self.etag = None
  146.             self.modified = None
  147.             self.dbItem.signalChange()
  148.         self.updating = False
  149.         if self.needsUpdate:
  150.             self.needsUpdate = False
  151.             self.requestUpdate()
  152.         elif error is not None:
  153.             addTimeout(3600,self.requestUpdate, "Thumbnail request for %s" % url)
  154.         else:
  155.             self.updated = True
  156.         iconCacheUpdater.updateFinished ()
  157.  
  158.     def updateIconCache (self, url, info):
  159.         self.dbItem.confirmDBThread()
  160.  
  161.         if self.removed:
  162.             iconCacheUpdater.updateFinished()
  163.             return
  164.  
  165.         needsSave = False
  166.         needsChange = False
  167.  
  168.         if info == None or (info['status'] != 304 and info['status'] != 200):
  169.             self.errorCallback(url)
  170.             return
  171.         try:
  172.             # Our cache is good.  Hooray!
  173.             if (info['status'] == 304):
  174.                 self.updated = True
  175.                 return
  176.  
  177.             needsChange = True
  178.  
  179.             # We have to update it, and if we can't write to the file, we
  180.             # should pick a new filename.
  181.             if (self.filename and not os.access (self.filename, os.R_OK | os.W_OK)):
  182.                 self.filename = None
  183.                 seedsSave = True
  184.  
  185.             cachedir = config.get(prefs.ICON_CACHE_DIRECTORY)
  186.             try:
  187.                 os.makedirs (cachedir)
  188.             except:
  189.                 pass
  190.  
  191.             try:
  192.                 # Write to a temp file.
  193.                 if (self.filename):
  194.                     tmp_filename = self.filename + ".part"
  195.                 else:
  196.                     tmp_filename = os.path.join(cachedir, info["filename"]) + ".part"
  197.  
  198.                 tmp_filename = nextFreeFilename (tmp_filename)
  199.                 output = file (tmp_filename, 'wb')
  200.                 output.write(info["body"])
  201.                 output.close()
  202.             except IOError:
  203.                 try:
  204.                     os.remove (tmp_filename)
  205.                 except:
  206.                     pass
  207.                 return
  208.  
  209.             if (self.filename == None):
  210.                 # Add a random unique id
  211.                 parts = unicodify(info["filename"]).split('.')
  212.                 uid = u"%08d" % (random.randint(0,99999999),)
  213.                 if len(parts) == 1:
  214.                     parts.append(uid)
  215.                 else:
  216.                     parts[-1:-1] = [uid]
  217.                 self.filename = u'.'.join(parts)
  218.                 self.filename = unicodeToFilename(self.filename, cachedir)
  219.                 self.filename = os.path.join(cachedir, self.filename)
  220.                 self.filename = nextFreeFilename (self.filename)
  221.                 needsSave = True
  222.             self._removeFile(self.filename)
  223.  
  224.             try:
  225.                 os.rename (tmp_filename, self.filename)
  226.             except:
  227.                 self.filename = None
  228.                 needsSave = True
  229.             else:
  230.                 self.resizeIcon()
  231.  
  232.  
  233.             if (info.has_key ("etag")):
  234.                 etag = unicodify(info["etag"])
  235.             else:
  236.                 etag = None
  237.  
  238.             if (info.has_key ("modified")):
  239.                 modified = unicodify(info["modified"])
  240.             else:
  241.                 modified = None
  242.  
  243.             if self.etag != etag:
  244.                 needsSave = True
  245.                 self.etag = etag
  246.             if self.modified != modified:
  247.                 needsSave = True
  248.                 self.modified = modified
  249.             if self.url != url:
  250.                 needsSave = True
  251.                 self.url = url
  252.             self.updated = True
  253.         finally:
  254.             if needsChange:
  255.                 self.dbItem.signalChange(needsSave=needsSave)
  256.             self.updating = False
  257.             if self.needsUpdate:
  258.                 self.needsUpdate = False
  259.                 self.requestUpdate()
  260.             iconCacheUpdater.updateFinished ()
  261.  
  262.     def requestIcon (self):
  263.         if self.removed:
  264.             iconCacheUpdater.updateFinished()
  265.             return
  266.  
  267.         self.dbItem.confirmDBThread()
  268.         if (self.updating):
  269.             self.needsUpdate = True
  270.             iconCacheUpdater.updateFinished ()
  271.             return
  272.         try:
  273.             url = self.dbItem.getThumbnailURL()
  274.         except:
  275.             url = self.url
  276.  
  277.         # Only verify each icon once per run unless the url changes
  278.         if (self.updated and url == self.url):
  279.             iconCacheUpdater.updateFinished ()
  280.             return
  281.  
  282.         self.updating = True
  283.  
  284.         # No need to extract the icon again if we already have it.
  285.         if url is None or url.startswith(u"/") or url.startswith(u"file://"):
  286.             self.errorCallback(url)
  287.             return
  288.  
  289.         # Last try, get the icon from HTTP.
  290.         if (url == self.url and self.filename and os.access (self.filename, os.R_OK)):
  291.             httpclient.grabURL (url, lambda info: self.updateIconCache(url, info), lambda error: self.errorCallback(url, error), etag=self.etag, modified=self.modified)
  292.         else:
  293.             httpclient.grabURL (url, lambda info: self.updateIconCache(url, info), lambda error: self.errorCallback(url, error))
  294.  
  295.     def requestUpdate (self, is_vital = False):
  296.         if hasattr (self, "updating") and hasattr (self, "dbItem"):
  297.             if self.removed:
  298.                 return
  299.  
  300.             iconCacheUpdater.requestUpdate (self, is_vital = is_vital)
  301.  
  302.     def onRestore(self):
  303.         self.removed = False
  304.         self.updated = False
  305.         self.updating = False
  306.         self.needsUpdate = False
  307.         self.requestUpdate ()
  308.  
  309.     def isValid(self):
  310.         self.dbItem.confirmDBThread()
  311.         return self.filename is not None and os.path.exists(self.filename)
  312.  
  313.     def getFilename(self):
  314.         self.dbItem.confirmDBThread()
  315.         if self.url and self.url.startswith (u"file://"):
  316.             return getFileURLPath(self.url)
  317.         elif self.url and self.url.startswith (u"/"):
  318.             return self.url
  319.         else:
  320.             return self.filename
  321.  
  322.     def getResizedFilename(self, width, height):
  323.         try:
  324.             return imageresize.getImage(self.resized_filenames, width, height)
  325.         except KeyError:
  326.             return self.getFilename()
  327.  
  328.     def resizeIcon(self):
  329.         imageresize.removeResizedFiles(self.resized_filenames)
  330.         self.resized_filenames = imageresize.multiResizeImage(self.filename,
  331.                 self.dbItem.ICON_CACHE_SIZES)
  332.