home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / xbmc-9.11.exe / scripts / AppleMovieTrailers / resources / lib / cacheurl.py next >
Encoding:
Python Source  |  2009-11-03  |  13.7 KB  |  332 lines

  1. """
  2. Url retrieve and cache module
  3.  
  4. Killarny
  5. """
  6.  
  7. import sys
  8. import os
  9. import xbmc
  10. import xbmcgui
  11. import urllib2
  12. import md5
  13. import time
  14. #import socket
  15. #socket.setdefaulttimeout( 20 )
  16.  
  17. #from keepalive import HTTPHandler
  18. #keepalive_handler = HTTPHandler()
  19. #opener = urllib2.build_opener( keepalive_handler )
  20. #urllib2.install_opener( opener )
  21.  
  22. from utilities import *
  23.  
  24.  
  25. __module_name__ = "cacheurl"
  26. __module_version__ = "0.1"
  27. #__useragent__ = "iTunes/4.7"
  28. __useragent__ = "QuickTime/7.2 (qtver=7.2;os=Windows NT 5.1Service Pack 3)"
  29.  
  30. _ = sys.modules[ "__main__" ].__language__
  31.  
  32.  
  33. def percent_from_ratio( top, bottom ):
  34.     return int( float( top ) / bottom * 100 )
  35.  
  36. def byte_measurement( bytes, detailed = False ):
  37.     """
  38.         convert integer bytes into a friendly string with B/kB/MB
  39.     """
  40.     B = bytes
  41.     MB = int( float( B ) / 1024 / 1024 )
  42.     B = B - MB * 1024 * 1024
  43.     kB = int( float( B ) / 1024 )
  44.     B = B - kB * 1024
  45.     if detailed:
  46.         if MB:
  47.             result += str( MB )
  48.             if kB or B:
  49.                 result += "."
  50.             if kB:
  51.                 result += str( percent_from_ratio( kB, 1024 ) )
  52.             if B:
  53.                 result += str( percent_from_ratio( B, 1024 ) )
  54.             result += "MB"
  55.         elif kB:
  56.             result += str( kB )
  57.             if B:
  58.                 result += ".%i" % percent_from_ratio( B, 1024 )
  59.             result += "kB"
  60.         elif B:
  61.             result += "%ib" % B
  62.     else:
  63.         if MB:
  64.             result = "%iMB" % MB
  65.         elif kB:
  66.             result = "%ikB" % kB
  67.         else:
  68.             result = "%ib" % B
  69.     return result
  70.  
  71. def rate_remaining( current_time_elapsed, size_read_so_far, filesize ):
  72.     """
  73.         calculates download rate and time remaining
  74.     """
  75.     total_time = int( current_time_elapsed / ( float( size_read_so_far ) / filesize ) - current_time_elapsed )
  76.     minutes = int( float( total_time ) / 60 )
  77.     seconds = total_time - ( minutes * 60 )
  78.     download_rate = "%d %s" % ( int( float( size_read_so_far ) / 1024 / current_time_elapsed ), _( 80 ) )
  79.     download_time_remaining = "%s: %02d:%02d" % ( _( 91 ), minutes, seconds, )
  80.     return download_rate, download_time_remaining
  81.  
  82. def set_status_symbol( status_symbol ):
  83.     status_symbol_index = [ "-", "\\", "|", "/" ].index( status_symbol ) + 1
  84.     if ( status_symbol_index >= 4 ):
  85.         status_symbol_index = 0
  86.     return [ "-", "\\", "|", "/" ][ status_symbol_index ]
  87.  
  88. class HTTP:
  89.     def __init__( self, cache = ".cache", title = "", actual_filename = False, flat_cache = False, clear_cache_folder=False ):
  90.         # set the cache directory; default to a .cache directory in BASE_DATA_PATH
  91.         self.cache_dir = cache
  92.         if self.cache_dir.startswith( "." ):
  93.             self.cache_dir = os.path.join( BASE_DATA_PATH, self.cache_dir )
  94.  
  95.         # title is the real name of the trailer, used for saving
  96.         self.title = title
  97.         
  98.         # flat_cache means that each request will be cached to a special folder; basically a non-persistent cache
  99.         self.flat_cache = flat_cache
  100.         self.clear_cache_folder = clear_cache_folder
  101.  
  102.         # normally, an md5 hexdigest will be used to determine a unique filename for each request, but with actual_filename set to True, the real filename will be used
  103.         # this can result in overwriting previously cached results if not used carefully
  104.         self.actual_filename = actual_filename
  105.  
  106.         # set default blocksize (this may require tweaking; cachedhttp1.3 had it set at 8192)
  107.         self.blocksize = 4096#16384#8192
  108.  
  109.     def clear_cache( self, cache_dir=None ):
  110.         try:
  111.             if cache_dir is None:
  112.                 cache_dir = self.cache_dir
  113.             for root, dirs, files in os.walk( cache_dir, topdown = False ):
  114.                 for name in files:
  115.                     os.remove( os.path.join( root, name ) )
  116.                 os.rmdir( root )
  117.         except:
  118.             LOG( LOG_ERROR, self.__class__.__name__, "%s.%s [%s]", __module_name__, __module_version__, sys.exc_info()[ 1 ] )
  119.  
  120.     def urlopen( self, url ):
  121.         # retrieve the file so it is cached
  122.         filepath = self.urlretrieve( url )
  123.         # read the data out of the file
  124.         data = ""
  125.         if filepath:
  126.             filehandle = open( filepath, "rb" )
  127.             data = filehandle.read()
  128.         return data
  129.  
  130.     def make_cache_filename( self, url ):
  131.         # construct the filename
  132.         if self.actual_filename:
  133.             filename = self.title
  134.             if self.flat_cache:
  135.                 filename = os.path.join( "flat_cache", filename )
  136.         else:
  137.             filename = md5.new( url ).hexdigest() + os.path.splitext( url )[ 1 ]
  138.             filename = os.path.join( filename[ 0 ], filename )
  139.  
  140.         # make the filepath legal
  141.         filepath = make_legal_filepath( os.path.join( self.cache_dir, filename ), conf=self.actual_filename )
  142.         return filepath
  143.  
  144.     def urlretrieve( self, url ):
  145.         self.info = {}
  146.         # get the filename
  147.         filepath = self.make_cache_filename( url )
  148.  
  149.         # if the file is already in the cache, return that filename
  150.         if os.path.isfile( filepath ):
  151.             # notify handler of being finished
  152.             self.on_finished( url, filepath, os.path.getsize( filepath ), True )
  153.             return filepath
  154.         
  155.         # if self.flat_cache delete existing flat_cache folder since this is a new movie
  156.         if self.flat_cache and self.clear_cache_folder:
  157.             self.clear_cache( os.path.join( self.cache_dir, "flat_cache" ) )
  158.  
  159.         try:
  160.             request = urllib2.Request( url )
  161.             request.add_header( "User-Agent", __useragent__ )
  162.             # hacky, I know, but the chances that the url will fail twice are slimmer than only trying once
  163.             #try:
  164.             opened = urllib2.urlopen( request )
  165.             #except:
  166.             #    opened = urllib2.urlopen( request )
  167.         except:
  168.             LOG( LOG_ERROR, self.__class__.__name__, "%s.%s [%s]", __module_name__, __module_version__, sys.exc_info()[ 1 ] )
  169.             self.on_finished( url, "", 0, False )
  170.             return None
  171.  
  172.         # this is the actual url that we got (redirection, etc)
  173.         actual_url = opened.geturl()
  174.         # info dict about the file (headers and such)
  175.         self.info = opened.info()
  176.  
  177.         # get the filename
  178.         if actual_url != url:
  179.             filepath = self.make_cache_filename( actual_url )
  180.         
  181.         # save the total expected size of the file, based on the Content-Length header
  182.         totalsize = int( self.info[ "Content-Length" ] )
  183.         try:
  184.             is_completed = os.path.getsize( filepath ) == totalsize
  185.         except:
  186.             is_completed = False
  187.  
  188.         # if the file is already in the cache, return that filename
  189.         if os.path.isfile( filepath ) and is_completed:
  190.             # notify handler of being finished
  191.             self.on_finished( actual_url, filepath, totalsize, is_completed )
  192.             return filepath
  193.  
  194.         # check if enough disk space exists to store the file
  195.         try:
  196.             drive = os.path.splitdrive( self.cache_dir )[ 0 ].split( ":" )[ 0 ]
  197.             free_space = xbmc.getInfoLabel( "System.Freespace(%s)" % drive )
  198.             if len( free_space.split() ) > 2:
  199.                 free_space_mb = int( free_space.split()[ 1 ] )
  200.                 free_space_b = free_space_mb * 1024 * 1024
  201.                 if totalsize >= free_space_b:
  202.                     header = _( 64 ) # Error
  203.                     line1 = _( 75 ) # Not enough free space in target drive.
  204.                     line2_drive = _( 76 ) # Drive
  205.                     line2_free = _( 77 ) # Free
  206.                     line2_space_required = _( 78 ) # Space Required
  207.                     xbmcgui.Dialog().ok( header, line1, line2_drive + ": %s %iMB " + line2_free % ( drive, free_space_mb ), line2_space_required + ": " + byte_measurement( totalsize ) )
  208.                     # notify handler of being finished
  209.                     self.on_finished( actual_url, filepath, totalsize, is_completed )
  210.                     return None
  211.         except:
  212.             LOG( LOG_ERROR, self.__class__.__name__, "%s.%s [%s]", __module_name__, __module_version__, sys.exc_info()[ 1 ] )
  213.             self.on_finished( url, "", 0, False )
  214.             return None
  215.             
  216.         # create the cache dir if it doesn't exist
  217.         if not os.path.isdir( os.path.split( filepath )[ 0 ] ):#self.cache_dir ):
  218.             os.makedirs( os.path.split( filepath )[ 0 ] )#self.cache_dir )
  219.  
  220.         # write the data to the cache
  221.         try:
  222.             filehandle = open( filepath, "wb" )
  223.             filedata = "..."
  224.             size_read_so_far = 0
  225.             do_continue = True
  226.             while len( filedata ):
  227.                 try:
  228.                     filedata = opened.read( self.blocksize )
  229.                     if len( filedata ):
  230.                         filehandle.write( filedata )
  231.                         size_read_so_far += len( filedata )
  232.                         do_continue = self.on_data( actual_url, filepath, totalsize, size_read_so_far )
  233.                     if len( filedata ) < self.blocksize:
  234.                         break
  235.                     if not do_continue:
  236.                         break
  237.                 except ( OSError, IOError ), ( errno, strerror ):
  238.                     # Error
  239.                     xbmcgui.Dialog().ok( _(64), "[%i] %s" % ( errno, strerror ) )
  240.                     break
  241.                 except:
  242.                     LOG( LOG_ERROR, self.__class__.__name__, "%s.%s [%s]", __module_name__, __module_version__, sys.exc_info()[ 1 ] )
  243.                     break
  244.             filehandle.close()
  245.         except:
  246.             raise
  247.  
  248.         try:
  249.             is_completed = os.path.getsize( filepath ) == totalsize
  250.         except:
  251.             is_completed = False
  252.  
  253.         # notify handler of being finished
  254.         self.on_finished( actual_url, filepath, totalsize, is_completed )
  255.  
  256.         # if the file transfer was halted before completing, remove the partial file from cache
  257.         if not is_completed:
  258.             os.remove( filepath )
  259.             filepath = None
  260.         return filepath
  261.  
  262.     def on_data( self, url, filepath, filesize, size_read_so_far ):
  263.         return True
  264.  
  265.     def on_finished( self, url, filepath, filesize, is_completed ):
  266.         pass
  267.  
  268.  
  269. class HTTPProgress( HTTP ):
  270.     def __init__( self, cache = ".cache", title = "", actual_filename = False, flat_cache = False, clear_cache_folder = False ):
  271.         HTTP.__init__( self, cache, title, actual_filename, flat_cache, clear_cache_folder )
  272.         self.dialog = xbmcgui.DialogProgress()
  273.         self.status_symbol = "-"
  274.         self.download_start_time = time.time()
  275.  
  276.     def urlretrieve( self, url ):
  277.         # Downloading...
  278.         self.dialog.create( _( 79 ), url.split( "/" )[ -1 ] )
  279.         self.dialog.update( 0 )
  280.         return HTTP.urlretrieve( self, url )
  281.  
  282.     def on_data( self, url, filepath, filesize, size_read_so_far ):
  283.         current_time_elapsed = time.time() - self.download_start_time
  284.         so_far = byte_measurement( size_read_so_far )
  285.         fsize = byte_measurement( filesize )
  286.         percentage = percent_from_ratio( size_read_so_far, filesize )
  287.         self.status_symbol = set_status_symbol( self.status_symbol )
  288.         download_rate, download_time_remaining = rate_remaining( current_time_elapsed, size_read_so_far, filesize )
  289.         self.dialog.update( percentage, url.split( "/" )[ -1 ], "%i%% (%s/%s) %s %s" % ( percentage, so_far, fsize, download_rate, self.status_symbol ), download_time_remaining )
  290.         if self.dialog.iscanceled():
  291.             return False
  292.         return True
  293.  
  294.     def on_finished( self, url, filepath, filesize, is_completed ):
  295.         self.dialog.close()
  296.  
  297. class HTTPProgressSave( HTTPProgress ):
  298.     def __init__( self, save_location = ".cache", save_title = "", clear_cache_folder = False ):
  299.         # if filepath is a samba share, save to the flat_cache directory, then copy it to the samba share
  300.         self.save_location = save_location
  301.         if ( save_location.startswith( "smb://" ) or save_location == ".cache" ):
  302.             flat_cache = True
  303.             save_location = ".cache"
  304.         else:
  305.             flat_cache = False
  306.         HTTPProgress.__init__( self, save_location, save_title, True, flat_cache, clear_cache_folder )
  307.  
  308.     def urlretrieve( self, url ):
  309.         filepath = HTTPProgress.urlretrieve( self, url )
  310.         if filepath is not None:
  311.             filepath = self.filepath
  312.         return filepath
  313.  
  314.     def on_finished( self, url, filepath, filesize, is_completed ):
  315.         self.filepath = filepath
  316.         if is_completed and os.path.splitext( filepath )[ 1 ] in [ ".mov", ".avi" ]:
  317.             try:
  318.                 # create conf file for better MPlayer playback
  319.                 if ( not os.path.isfile( filepath + ".conf" ) ):
  320.                     f = open( filepath + ".conf" , "w" )
  321.                     f.write( "nocache=1" )
  322.                     f.close()
  323.                 # if save location is a samba share, copy the file
  324.                 if ( self.save_location.startswith( "smb://" ) ):
  325.                     self.dialog.update( -1, _( 56 ), _( 67 ), "" )
  326.                     self.filepath = os.path.join( self.save_location, os.path.basename( filepath ) )
  327.                     xbmc.executehttpapi("FileCopy(%s,%s)" % ( filepath, self.filepath, ) )
  328.                     #xbmc.executehttpapi("FileCopy(%s.conf,%s.conf)" % ( filepath, self.filepath, ) )
  329.             except:
  330.                 LOG( LOG_ERROR, self.__class__.__name__, "%s.%s [%s]", __module_name__, __module_version__, sys.exc_info()[ 1 ] )
  331.         HTTPProgress.on_finished( self, url, self.filepath, filesize, is_completed )
  332.