home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / Miro_Downloader.exe / dl_daemon / download.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2007-11-12  |  26.5 KB  |  944 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. import os
  5. import sys
  6. import bsddb
  7. import shutil
  8. import types
  9. from gettext import gettext as _
  10. from os import remove
  11. import re
  12. from threading import RLock, Event, Thread
  13. import traceback
  14. from copy import copy
  15. from BitTorrent.bencode import bdecode
  16. from clock import clock
  17. from download_utils import cleanFilename, nextFreeFilename, checkFilenameExtension, filterDirectoryName
  18. from download_utils import filenameFromURL, getFileURLPath
  19. import eventloop
  20. import httpclient
  21. import datetime
  22. import logging
  23. import fileutil
  24. import config
  25. import prefs
  26. from sha import sha
  27. from dl_daemon import command, daemon, bittorrentdtv
  28. from datetime import timedelta
  29. from util import checkF, checkU, stringify
  30. import platformutils
  31. import string
  32. chatter = True
  33. _downloads = { }
  34. _lock = RLock()
  35.  
  36. def createDownloader(url, contentType, dlid):
  37.     checkU(url)
  38.     checkU(contentType)
  39.     if contentType == u'application/x-bittorrent':
  40.         return BTDownloader(url, dlid)
  41.     else:
  42.         return HTTPDownloader(url, dlid)
  43.  
  44.  
  45. def startNewDownload(url, dlid, contentType, channelName):
  46.     checkU(url)
  47.     checkU(contentType)
  48.     if channelName:
  49.         checkF(channelName)
  50.     
  51.     dl = createDownloader(url, contentType, dlid)
  52.     dl.channelName = channelName
  53.     _downloads[dlid] = dl
  54.  
  55.  
  56. def pauseDownload(dlid):
  57.     
  58.     try:
  59.         download = _downloads[dlid]
  60.     except:
  61.         return True
  62.  
  63.     return download.pause()
  64.  
  65.  
  66. def startDownload(dlid):
  67.     
  68.     try:
  69.         download = _downloads[dlid]
  70.     except KeyError:
  71.         err = u'in startDownload(): no downloader with id %s' % dlid
  72.         c = command.DownloaderErrorCommand(daemon.lastDaemon, err)
  73.         c.send()
  74.         return True
  75.  
  76.     return download.start()
  77.  
  78.  
  79. def stopDownload(dlid, delete):
  80.     
  81.     try:
  82.         _lock.acquire()
  83.         
  84.         try:
  85.             download = _downloads[dlid]
  86.             del _downloads[dlid]
  87.         finally:
  88.             _lock.release()
  89.  
  90.     except:
  91.         return True
  92.  
  93.     return download.stop(delete)
  94.  
  95.  
  96. def stopUpload(dlid):
  97.     
  98.     try:
  99.         _lock.acquire()
  100.         
  101.         try:
  102.             download = _downloads[dlid]
  103.             if download.state != u'uploading':
  104.                 return None
  105.             
  106.             del _downloads[dlid]
  107.         finally:
  108.             _lock.release()
  109.  
  110.     except:
  111.         return None
  112.  
  113.     return download.stopUpload()
  114.  
  115.  
  116. def migrateDownload(dlid, directory):
  117.     checkF(directory)
  118.     
  119.     try:
  120.         download = _downloads[dlid]
  121.     except:
  122.         pass
  123.  
  124.     if download.state in (u'finished', u'uploading'):
  125.         download.moveToDirectory(directory)
  126.     
  127.  
  128.  
  129. def getDownloadStatus(dlids = None):
  130.     statuses = { }
  131.     for key in _downloads.keys():
  132.         if dlids is None and dlids == key or key in dlids:
  133.             
  134.             try:
  135.                 statuses[key] = _downloads[key].getStatus()
  136.  
  137.             continue
  138.     
  139.     return statuses
  140.  
  141.  
  142. def shutDown():
  143.     logging.info('Shutting down downloaders...')
  144.     for dlid in _downloads:
  145.         _downloads[dlid].shutdown()
  146.     
  147.  
  148.  
  149. def restoreDownloader(downloader):
  150.     downloader = copy(downloader)
  151.     dlerType = downloader.get('dlerType')
  152.     if dlerType == u'HTTP':
  153.         dl = HTTPDownloader(restore = downloader)
  154.     elif dlerType == u'BitTorrent':
  155.         dl = BTDownloader(restore = downloader)
  156.     else:
  157.         err = u'in restoreDownloader(): unknown dlerType: %s' % dlerType
  158.         c = command.DownloaderErrorCommand(daemon.lastDaemon, err)
  159.         c.send()
  160.         return None
  161.     _downloads[downloader['dlid']] = dl
  162.  
  163.  
  164. class DownloadStatusUpdater:
  165.     """Handles updating status for all in progress downloaders.
  166.  
  167.     On OS X and gtk if the user is on the downloads page and has a bunch of
  168.     downloads going, this can be a fairly CPU intensive task.
  169.     DownloadStatusUpdaters mitigate this in 2 ways.
  170.  
  171.     1) DownloadStatusUpdater objects batch all status updates into one big
  172.     update which takes much less CPU.  
  173.     
  174.     2) The update don't happen fairly infrequently (currently every 5 seconds).
  175.     
  176.     Becouse updates happen infrequently, DownloadStatusUpdaters should only be
  177.     used for progress updates, not events like downloads starting/finishing.
  178.     For those just call updateClient() since they are more urgent, and don't
  179.     happen often enough to cause CPU problems.
  180.     """
  181.     UPDATE_CLIENT_INTERVAL = 5
  182.     
  183.     def __init__(self):
  184.         self.toUpdate = set()
  185.  
  186.     
  187.     def startUpdates(self):
  188.         eventloop.addTimeout(self.UPDATE_CLIENT_INTERVAL, self.doUpdate, 'Download status update')
  189.  
  190.     
  191.     def doUpdate(self):
  192.         
  193.         try:
  194.             statuses = []
  195.             for downloader in self.toUpdate:
  196.                 statuses.append(downloader.getStatus())
  197.             
  198.             self.toUpdate = set()
  199.             if statuses:
  200.                 command.BatchUpdateDownloadStatus(daemon.lastDaemon, statuses).send()
  201.         finally:
  202.             eventloop.addTimeout(self.UPDATE_CLIENT_INTERVAL, self.doUpdate, 'Download status update')
  203.  
  204.  
  205.     
  206.     def queueUpdate(self, downloader):
  207.         self.toUpdate.add(downloader)
  208.  
  209.  
  210. downloadUpdater = DownloadStatusUpdater()
  211. RETRY_TIMES = (60, 5 * 60, 10 * 60, 30 * 60, 60 * 60, 2 * 60 * 60, 6 * 60 * 60, 24 * 60 * 60)
  212.  
  213. class BGDownloader:
  214.     
  215.     def __init__(self, url, dlid):
  216.         self.dlid = dlid
  217.         self.url = url
  218.         self.startTime = clock()
  219.         self.endTime = self.startTime
  220.         self.shortFilename = filenameFromURL(url)
  221.         self.pickInitialFilename()
  222.         self.state = u'downloading'
  223.         self.currentSize = 0
  224.         self.totalSize = -1
  225.         self.blockTimes = []
  226.         self.shortReasonFailed = self.reasonFailed = u'No Error'
  227.         self.retryTime = None
  228.         self.retryCount = -1
  229.  
  230.     
  231.     def getURL(self):
  232.         return self.url
  233.  
  234.     
  235.     def getStatus(self):
  236.         return {
  237.             'dlid': self.dlid,
  238.             'url': self.url,
  239.             'state': self.state,
  240.             'totalSize': self.totalSize,
  241.             'currentSize': self.currentSize,
  242.             'eta': self.getETA(),
  243.             'rate': self.getRate(),
  244.             'uploaded': 0,
  245.             'filename': self.filename,
  246.             'startTime': self.startTime,
  247.             'endTime': self.endTime,
  248.             'shortFilename': self.shortFilename,
  249.             'reasonFailed': self.reasonFailed,
  250.             'shortReasonFailed': self.shortReasonFailed,
  251.             'dlerType': None,
  252.             'retryTime': self.retryTime,
  253.             'retryCount': self.retryCount,
  254.             'channelName': self.channelName }
  255.  
  256.     
  257.     def updateClient(self):
  258.         x = command.UpdateDownloadStatus(daemon.lastDaemon, self.getStatus())
  259.         return x.send()
  260.  
  261.     
  262.     def pickInitialFilename(self):
  263.         '''Pick a path to download to based on self.shortFilename.
  264.  
  265.         This method sets self.filename, as well as creates any leading paths
  266.         needed to start downloading there.
  267.         '''
  268.         downloadDir = os.path.join(config.get(prefs.MOVIES_DIRECTORY), 'Incomplete Downloads')
  269.         
  270.         try:
  271.             os.makedirs(downloadDir)
  272.         except:
  273.             pass
  274.  
  275.         cleaned = cleanFilename(self.shortFilename + '.part')
  276.         self.filename = nextFreeFilename(os.path.join(downloadDir, cleaned))
  277.  
  278.     
  279.     def moveToMoviesDirectory(self):
  280.         '''Move our downloaded file from the Incomplete Downloads directoy to
  281.         the movies directory.
  282.         '''
  283.         if chatter:
  284.             logging.info('moving to movies directory filename is %s', self.filename)
  285.         
  286.         self.moveToDirectory(config.get(prefs.MOVIES_DIRECTORY))
  287.  
  288.     
  289.     def moveToDirectory(self, directory):
  290.         checkF(directory)
  291.         if self.channelName:
  292.             channelName = filterDirectoryName(self.channelName)
  293.             directory = os.path.join(directory, channelName)
  294.             
  295.             try:
  296.                 os.makedirs(directory)
  297.  
  298.         
  299.         newfilename = os.path.join(directory, self.shortFilename)
  300.         if newfilename == self.filename:
  301.             return None
  302.         
  303.         newfilename = nextFreeFilename(newfilename)
  304.         
  305.         def callback():
  306.             self.filename = newfilename
  307.             self.updateClient()
  308.  
  309.         fileutil.migrate_file(self.filename, newfilename, callback)
  310.  
  311.     
  312.     def getETA(self):
  313.         if self.totalSize == -1:
  314.             return -1
  315.         
  316.         rate = self.getRate()
  317.         if rate > 0:
  318.             return (self.totalSize - self.currentSize) / rate
  319.         else:
  320.             return 0
  321.  
  322.     
  323.     def getRate(self):
  324.         now = clock()
  325.         if self.endTime != self.startTime:
  326.             rate = self.currentSize / (self.endTime - self.startTime)
  327.         else:
  328.             haltedSince = now
  329.             for time, size in reversed(self.blockTimes):
  330.                 if size == self.currentSize:
  331.                     haltedSince = time
  332.                     continue
  333.             
  334.             if now - haltedSince > self.HALTED_THRESHOLD:
  335.                 rate = 0
  336.             else:
  337.                 
  338.                 try:
  339.                     timespan = now - self.blockTimes[0][0]
  340.                     if timespan != 0:
  341.                         endSize = self.blockTimes[-1][1]
  342.                         startSize = self.blockTimes[0][1]
  343.                         rate = (endSize - startSize) / timespan
  344.                     else:
  345.                         rate = 0
  346.                 except IndexError:
  347.                     rate = 0
  348.  
  349.         return rate
  350.  
  351.     
  352.     def retryDownload(self):
  353.         self.retryDC = None
  354.         self.start()
  355.  
  356.     
  357.     def handleTemporaryError(self, shortReason, reason):
  358.         self.state = u'offline'
  359.         self.reasonFailed = reason
  360.         self.shortReasonFailed = shortReason
  361.         self.retryCount = self.retryCount + 1
  362.         if self.retryCount >= len(RETRY_TIMES):
  363.             self.retryCount = len(RETRY_TIMES) - 1
  364.         
  365.         self.retryDC = eventloop.addTimeout(RETRY_TIMES[self.retryCount], self.retryDownload, 'Logarithmic retry')
  366.         self.retryTime = datetime.datetime.now() + datetime.timedelta(seconds = RETRY_TIMES[self.retryCount])
  367.         self.updateClient()
  368.  
  369.     
  370.     def handleError(self, shortReason, reason):
  371.         self.state = u'failed'
  372.         self.reasonFailed = reason
  373.         self.shortReasonFailed = shortReason
  374.         self.updateClient()
  375.  
  376.     
  377.     def handleNetworkError(self, error):
  378.         if isinstance(error, httpclient.NetworkError):
  379.             if isinstance(error, httpclient.MalformedURL) or isinstance(error, httpclient.UnexpectedStatusCode):
  380.                 self.handleError(error.getFriendlyDescription(), error.getLongDescription())
  381.             else:
  382.                 self.handleTemporaryError(error.getFriendlyDescription(), error.getLongDescription())
  383.         else:
  384.             logging.info('WARNING: grabURL errback not called with NetworkError')
  385.             self.handleError(str(error), str(error))
  386.  
  387.     
  388.     def handleGenericError(self, longDescription):
  389.         self.handleError(_('Error'), longDescription)
  390.  
  391.  
  392.  
  393. class HTTPDownloader(BGDownloader):
  394.     UPDATE_CLIENT_WINDOW = 12
  395.     HALTED_THRESHOLD = 3
  396.     
  397.     def __init__(self, url = None, dlid = None, restore = None):
  398.         self.retryDC = None
  399.         self.channelName = None
  400.         if restore is not None:
  401.             if not isinstance(restore.get('totalSize', 0), int):
  402.                 restore = None
  403.             
  404.         
  405.         if restore is not None:
  406.             self.__dict__.update(restore)
  407.             self.blockTimes = []
  408.             self.restartOnError = True
  409.         else:
  410.             BGDownloader.__init__(self, url, dlid)
  411.             self.restartOnError = False
  412.         self.client = None
  413.         self.filehandle = None
  414.         if self.state == 'downloading':
  415.             self.startDownload()
  416.         elif self.state == 'offline':
  417.             self.start()
  418.         else:
  419.             self.updateClient()
  420.  
  421.     
  422.     def resetBlockTimes(self):
  423.         self.blockTimes = [
  424.             (clock(), self.currentSize)]
  425.  
  426.     
  427.     def startNewDownload(self):
  428.         self.currentSize = 0
  429.         self.totalSize = -1
  430.         self.startDownload()
  431.  
  432.     
  433.     def startDownload(self):
  434.         if self.retryDC:
  435.             self.retryDC.cancel()
  436.             self.retryDC = None
  437.         
  438.         if self.currentSize == 0:
  439.             headerCallback = self.onHeaders
  440.         else:
  441.             headerCallback = self.onHeadersRestart
  442.         self.client = httpclient.grabURL(self.url, self.onDownloadFinished, self.onDownloadError, headerCallback, self.onBodyData, start = self.currentSize)
  443.         self.resetBlockTimes()
  444.         self.updateClient()
  445.  
  446.     
  447.     def cancelRequest(self):
  448.         if self.client is not None:
  449.             self.client.cancel()
  450.             self.client = None
  451.         
  452.  
  453.     
  454.     def handleError(self, shortReason, reason):
  455.         BGDownloader.handleError(self, shortReason, reason)
  456.         self.cancelRequest()
  457.         
  458.         try:
  459.             remove(self.filename)
  460.         except:
  461.             pass
  462.  
  463.         self.currentSize = 0
  464.         self.totalSize = -1
  465.  
  466.     
  467.     def handleTemporaryError(self, shortReason, reason):
  468.         BGDownloader.handleTemporaryError(self, shortReason, reason)
  469.         self.cancelRequest()
  470.  
  471.     
  472.     def handleWriteError(self, error):
  473.         self.handleGenericError(_('Could not write to %s') % stringify(self.filename))
  474.         if self.filehandle is not None:
  475.             
  476.             try:
  477.                 self.filehandle.close()
  478.  
  479.         
  480.         
  481.         try:
  482.             remove(self.filename)
  483.         except:
  484.             pass
  485.  
  486.  
  487.     
  488.     def onHeaders(self, info):
  489.         if info['contentLength'] != None:
  490.             self.totalSize = info['contentLength']
  491.         
  492.         if self.client.gotBadStatusCode:
  493.             error = httpclient.UnexpectedStatusCode(info['status'])
  494.             self.handleNetworkError(error)
  495.             return None
  496.         
  497.         if not self.acceptDownloadSize(self.totalSize):
  498.             self.handleError(_('Not enough disk space'), _('%s MB required to store this video') % self.totalSize / 1048576)
  499.             return None
  500.         
  501.         self.retryCount = -1
  502.         self.shortFilename = cleanFilename(info['filename'])
  503.         self.shortFilename = checkFilenameExtension(self.shortFilename, info)
  504.         self.pickInitialFilename()
  505.         
  506.         try:
  507.             self.filehandle = file(self.filename, 'w+b')
  508.         except IOError:
  509.             self.handleGenericError("Couldn't open %s for writing" % stringify(self.filename))
  510.             return None
  511.  
  512.         if self.totalSize > 0:
  513.             
  514.             try:
  515.                 self.filehandle.seek(self.totalSize - 1)
  516.                 self.filehandle.write(' ')
  517.                 self.filehandle.seek(0)
  518.             except IOError:
  519.                 error = None
  520.                 self.handleWriteError(error)
  521.                 return None
  522.             except:
  523.                 None<EXCEPTION MATCH>IOError
  524.             
  525.  
  526.         None<EXCEPTION MATCH>IOError
  527.         self.updateClient()
  528.  
  529.     
  530.     def onHeadersRestart(self, info):
  531.         self.restartOnError = False
  532.         if info['status'] != 206 or 'content-range' not in info:
  533.             self.currentSize = 0
  534.             self.totalSize = -1
  535.             self.resetBlockTimes()
  536.             if not self.client.gotBadStatusCode:
  537.                 self.onHeaders(info)
  538.             else:
  539.                 self.cancelRequest()
  540.                 self.startNewDownload()
  541.             return None
  542.         
  543.         
  544.         try:
  545.             self.parseContentRange(info['content-range'])
  546.         except ValueError:
  547.             logging.info('WARNING, bad content-range: %r', info['content-range'])
  548.             logging.info('currentSize: %d totalSize: %d', self.currentSize, self.totalSize)
  549.             self.cancelRequest()
  550.             self.startNewDownload()
  551.  
  552.         
  553.         try:
  554.             self.filehandle = file(self.filename, 'r+b')
  555.             self.filehandle.seek(self.currentSize)
  556.         except IOError:
  557.             e = None
  558.             self.handleWriteError(e)
  559.  
  560.         self.retryCount = -1
  561.         self.updateClient()
  562.  
  563.     
  564.     def parseContentRange(self, contentRange):
  565.         """Parse the content-range header from an http response.  If it's
  566.         badly formatted, or it's not what we were expecting based on the state
  567.         we restored to, raise a ValueError.
  568.         """
  569.         m = re.search('bytes\\s+(\\d+)-(\\d+)/(\\d+)', contentRange)
  570.         if m is None:
  571.             raise ValueError()
  572.         
  573.         start = int(m.group(1))
  574.         end = int(m.group(2))
  575.         totalSize = int(m.group(3))
  576.         if start > self.currentSize or end + 1 != totalSize:
  577.             raise ValueError()
  578.         
  579.         self.currentSize = start
  580.         self.totalSize = totalSize
  581.  
  582.     
  583.     def onDownloadError(self, error):
  584.         if self.restartOnError:
  585.             self.restartOnError = False
  586.             self.startDownload()
  587.         else:
  588.             self.client = None
  589.             self.handleNetworkError(error)
  590.  
  591.     
  592.     def onBodyData(self, data):
  593.         if self.state != 'downloading':
  594.             return None
  595.         
  596.         self.updateRateAndETA(len(data))
  597.         downloadUpdater.queueUpdate(self)
  598.         
  599.         try:
  600.             self.filehandle.write(data)
  601.         except IOError:
  602.             e = None
  603.             self.handleWriteError(e)
  604.  
  605.  
  606.     
  607.     def onDownloadFinished(self, response):
  608.         self.client = None
  609.         
  610.         try:
  611.             self.filehandle.close()
  612.         except Exception:
  613.             e = None
  614.             self.handleWriteError(e)
  615.             return None
  616.  
  617.         self.state = 'finished'
  618.         if self.totalSize == -1:
  619.             self.totalSize = self.currentSize
  620.         
  621.         self.endTime = clock()
  622.         
  623.         try:
  624.             self.moveToMoviesDirectory()
  625.         except IOError:
  626.             e = None
  627.             self.handleWriteError(e)
  628.  
  629.         self.resetBlockTimes()
  630.         self.updateClient()
  631.  
  632.     
  633.     def getStatus(self):
  634.         data = BGDownloader.getStatus(self)
  635.         data['dlerType'] = 'HTTP'
  636.         return data
  637.  
  638.     
  639.     def updateRateAndETA(self, length):
  640.         now = clock()
  641.         self.currentSize = self.currentSize + length
  642.         self.blockTimes.append((now, self.currentSize))
  643.         window_start = now - self.UPDATE_CLIENT_WINDOW
  644.         i = 0
  645.         for i in xrange(len(self.blockTimes)):
  646.             if self.blockTimes[0][0] >= window_start:
  647.                 break
  648.                 continue
  649.         
  650.         self.blockTimes = self.blockTimes[i:]
  651.  
  652.     
  653.     def acceptDownloadSize(self, size):
  654.         accept = True
  655.         if config.get(prefs.PRESERVE_DISK_SPACE):
  656.             if size < 0:
  657.                 size = 0
  658.             
  659.             preserved = config.get(prefs.PRESERVE_X_GB_FREE) * 1024 * 1024 * 1024
  660.             available = platformutils.getAvailableBytesForMovies() - preserved
  661.             accept = size <= available
  662.         
  663.         return accept
  664.  
  665.     
  666.     def pause(self):
  667.         if self.state != 'stopped':
  668.             self.cancelRequest()
  669.             self.state = 'paused'
  670.             self.updateClient()
  671.         
  672.  
  673.     
  674.     def stop(self, delete):
  675.         if self.state == 'downloading':
  676.             if self.filehandle is not None:
  677.                 
  678.                 try:
  679.                     if not self.filehandle.closed:
  680.                         self.filehandle.close()
  681.                     
  682.                     remove(self.filename)
  683.  
  684.             
  685.         
  686.         if delete:
  687.             
  688.             try:
  689.                 if os.path.isdir(self.filename):
  690.                     shutil.rmtree(self.filename)
  691.                 else:
  692.                     remove(self.filename)
  693.  
  694.         
  695.         self.currentSize = 0
  696.         self.cancelRequest()
  697.         self.state = 'stopped'
  698.         self.updateClient()
  699.  
  700.     
  701.     def stopUpload(self):
  702.         pass
  703.  
  704.     
  705.     def start(self):
  706.         if self.state in ('paused', 'stopped', 'offline'):
  707.             self.state = 'downloading'
  708.             self.startDownload()
  709.         
  710.  
  711.     
  712.     def shutdown(self):
  713.         self.cancelRequest()
  714.         self.updateClient()
  715.  
  716.  
  717.  
  718. class BTDownloader(BGDownloader):
  719.     
  720.     def __init__(self, url = None, item = None, restore = None):
  721.         self.metainfo = None
  722.         self.torrent = None
  723.         self.rate = self.eta = 0
  724.         self.upRate = self.uploaded = 0
  725.         self.activity = None
  726.         self.fastResumeData = None
  727.         self.retryDC = None
  728.         self.channelName = None
  729.         self.uploadedStart = 0
  730.         self.restarting = False
  731.         if restore is not None:
  732.             self.restoreState(restore)
  733.         else:
  734.             BGDownloader.__init__(self, url, item)
  735.             self.runDownloader()
  736.  
  737.     
  738.     def _shutdownTorrent(self):
  739.         
  740.         try:
  741.             if self.torrent is not None:
  742.                 self.fastResumeData = self.torrent.shutdown()
  743.         except:
  744.             logging.exception('DTV: Warning: Error shutting down torrent')
  745.  
  746.  
  747.     
  748.     def _startTorrent(self):
  749.         
  750.         try:
  751.             self.torrent = bittorrentdtv.TorrentDownload(self.metainfo, self.filename, self.fastResumeData)
  752.         except:
  753.             self.handleError(_('BitTorrent failure'), _('BitTorrent failed to startup'))
  754.  
  755.         self.torrent.set_status_callback(self.updateStatus)
  756.         self.torrent.start()
  757.  
  758.     
  759.     def updateStatus(self, newStatus):
  760.         """
  761.         activity -- string specifying what's currently happening or None for
  762.                 normal operations.  
  763.         upRate -- upload rate in B/s
  764.         downRate -- download rate in B/s
  765.         upTotal -- total MB uploaded
  766.         downTotal -- total MB downloaded
  767.         fractionDone -- what portion of the download is completed.
  768.         timeEst -- estimated completion time, in seconds.
  769.         totalSize -- total size of the torrent in bytes
  770.         """
  771.         self.totalSize = newStatus['totalSize']
  772.         self.rate = newStatus['downRate']
  773.         self.upRate = newStatus['upRate']
  774.         self.uploaded = newStatus['upTotal'] + self.uploadedStart
  775.         self.eta = newStatus['timeEst']
  776.         self.activity = newStatus['activity']
  777.         self.currentSize = int(self.totalSize * newStatus['fractionDone'])
  778.         if self.state == 'downloading' and newStatus['fractionDone'] == 1:
  779.             self.moveToMoviesDirectory()
  780.             self.state = 'uploading'
  781.             self.endTime = clock()
  782.             self.updateClient()
  783.         else:
  784.             downloadUpdater.queueUpdate(self)
  785.  
  786.     updateStatus = eventloop.asIdle(updateStatus)
  787.     
  788.     def handleError(self, shortReason, reason):
  789.         self._shutdownTorrent()
  790.         BGDownloader.handleError(self, shortReason, reason)
  791.  
  792.     
  793.     def handleTemporaryError(self, shortReason, reason):
  794.         self._shutdownTorrent()
  795.         BGDownloader.handleTemporaryError(self, shortReason, reason)
  796.  
  797.     
  798.     def moveToDirectory(self, directory):
  799.         if self.state in ('uploading', 'downloading'):
  800.             self._shutdownTorrent()
  801.             BGDownloader.moveToDirectory(self, directory)
  802.             self._startTorrent()
  803.         else:
  804.             BGDownloader.moveToDirectory(self, directory)
  805.  
  806.     
  807.     def restoreState(self, data):
  808.         self.__dict__.update(data)
  809.         self.rate = self.eta = 0
  810.         self.upRate = 0
  811.         self.uploadedStart = self.uploaded
  812.         if self.state in ('downloading', 'uploading'):
  813.             self.runDownloader(done = True)
  814.         elif self.state == 'offline':
  815.             self.start()
  816.         
  817.  
  818.     
  819.     def getStatus(self):
  820.         data = BGDownloader.getStatus(self)
  821.         data['upRate'] = self.upRate
  822.         data['uploaded'] = self.uploaded
  823.         data['metainfo'] = self.metainfo
  824.         data['fastResumeData'] = self.fastResumeData
  825.         data['activity'] = self.activity
  826.         data['dlerType'] = 'BitTorrent'
  827.         return data
  828.  
  829.     
  830.     def getRate(self):
  831.         return self.rate
  832.  
  833.     
  834.     def getETA(self):
  835.         return self.eta
  836.  
  837.     
  838.     def pause(self):
  839.         self.state = 'paused'
  840.         self._shutdownTorrent()
  841.         self.updateClient()
  842.  
  843.     
  844.     def stop(self, delete):
  845.         self.state = 'stopped'
  846.         self._shutdownTorrent()
  847.         self.updateClient()
  848.         if delete:
  849.             
  850.             try:
  851.                 if os.path.isdir(self.filename):
  852.                     shutil.rmtree(self.filename)
  853.                 else:
  854.                     remove(self.filename)
  855.  
  856.         
  857.  
  858.     
  859.     def stopUpload(self):
  860.         self.state = 'finished'
  861.         self._shutdownTorrent()
  862.         self.updateClient()
  863.  
  864.     
  865.     def start(self):
  866.         if self.state not in ('paused', 'stopped', 'offline'):
  867.             return None
  868.         
  869.         self.state = 'downloading'
  870.         if self.retryDC:
  871.             self.retryDC.cancel()
  872.             self.retryDC = None
  873.         
  874.         self.updateClient()
  875.         self.getMetainfo()
  876.  
  877.     
  878.     def shutdown(self):
  879.         self._shutdownTorrent()
  880.         self.updateClient()
  881.  
  882.     
  883.     def gotMetainfo(self):
  884.         if not self.restarting:
  885.             
  886.             try:
  887.                 metainfo = bdecode(self.metainfo)
  888.                 name = metainfo['info']['name']
  889.             except (ValueError, KeyError):
  890.                 self.handleError(_('Corrupt Torrent'), _('The torrent file at %s was not valid') % stringify(self.url))
  891.                 return None
  892.  
  893.             name = name.decode('utf-8', 'replace')
  894.             self.shortFilename = cleanFilename(name)
  895.             self.pickInitialFilename()
  896.         
  897.         self.updateClient()
  898.         self._startTorrent()
  899.  
  900.     
  901.     def handleMetainfo(self, metainfo):
  902.         self.metainfo = metainfo
  903.         self.gotMetainfo()
  904.  
  905.     
  906.     def onDescriptionDownload(self, info):
  907.         self.handleMetainfo(info['body'])
  908.  
  909.     
  910.     def onDescriptionDownloadFailed(self, exception):
  911.         self.handleNetworkError(exception)
  912.  
  913.     
  914.     def getMetainfo(self):
  915.         if self.metainfo is None:
  916.             if self.url.startswith('file://'):
  917.                 path = getFileURLPath(self.url)
  918.                 
  919.                 try:
  920.                     metainfoFile = open(path, 'rb')
  921.                 except IOError:
  922.                     self.handleError(_('Torrent file deleted'), _('The torrent file for this item was deleted outside of Miro.'))
  923.                     return None
  924.  
  925.                 
  926.                 try:
  927.                     metainfo = metainfoFile.read()
  928.                 finally:
  929.                     metainfoFile.close()
  930.  
  931.                 self.handleMetainfo(metainfo)
  932.             else:
  933.                 httpclient.grabURL(self.getURL(), self.onDescriptionDownload, self.onDescriptionDownloadFailed)
  934.         else:
  935.             self.gotMetainfo()
  936.  
  937.     
  938.     def runDownloader(self, done = False):
  939.         self.restarting = done
  940.         self.updateClient()
  941.         self.getMetainfo()
  942.  
  943.  
  944.