download.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from os import remove, stat
  2. from os.path import basename, isdir, isfile, join as joinpath
  3. from urllib import FancyURLopener
  4. from urlparse import urlparse
  5. import sys
  6. # FancyURLOpener, which is also used in urlretrieve(), does not raise
  7. # an exception on status codes like 404 and 500. However, for downloading it is
  8. # critical to write either what we requested or nothing at all.
  9. class DownloadURLOpener(FancyURLopener):
  10. def http_error_default(self, url, fp, errcode, errmsg, headers):
  11. raise IOError('%s: http:%s' % (errmsg, url))
  12. _urlOpener = DownloadURLOpener()
  13. class StatusLine(object):
  14. def __init__(self, out):
  15. self._out = out
  16. def __call__(self, message, progress = False):
  17. raise NotImplementedError
  18. class InteractiveStatusLine(StatusLine):
  19. __length = 0
  20. def __call__(self, message, progress = False):
  21. self._out.write(('\r%-' + str(self.__length) + 's') % message)
  22. self.__length = max(self.__length, len(message))
  23. class NoninteractiveStatusLine(StatusLine):
  24. def __call__(self, message, progress = False):
  25. if not progress:
  26. self._out.write(message + '\n')
  27. def createStatusLine(out):
  28. if out.isatty():
  29. return InteractiveStatusLine(out)
  30. else:
  31. return NoninteractiveStatusLine(out)
  32. def downloadURL(url, localDir):
  33. if not isdir(localDir):
  34. raise IOError('Local directory "%s" does not exist' % localDir)
  35. fileName = basename(urlparse(url).path)
  36. localPath = joinpath(localDir, fileName)
  37. prefix = 'Downloading %s: ' % fileName
  38. statusLine = createStatusLine(sys.stdout)
  39. statusLine(prefix + 'contacting server...')
  40. def reportProgress(blocksDone, blockSize, totalSize):
  41. doneSize = blocksDone * blockSize
  42. statusLine(prefix + (
  43. '%d/%d bytes (%1.1f%%)...' % (
  44. doneSize, totalSize, (100.0 * doneSize) / totalSize
  45. )
  46. if totalSize > 0 else
  47. '%d bytes...' % doneSize
  48. ), True)
  49. try:
  50. try:
  51. _urlOpener.retrieve(url, localPath, reportProgress)
  52. except IOError:
  53. statusLine(prefix + 'FAILED.')
  54. raise
  55. else:
  56. statusLine(prefix + 'done.')
  57. finally:
  58. print
  59. except:
  60. if isfile(localPath):
  61. statusLine(prefix + 'removing partial download.')
  62. remove(localPath)
  63. raise
  64. if __name__ == '__main__':
  65. if len(sys.argv) == 3:
  66. try:
  67. downloadURL(*sys.argv[1 : ])
  68. except IOError, ex:
  69. print >> sys.stderr, ex
  70. sys.exit(1)
  71. else:
  72. print >> sys.stderr, \
  73. 'Usage: python download.py url localdir'
  74. sys.exit(2)