download.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. from os import remove, stat
  2. from os.path import basename, isdir, isfile, join as joinpath
  3. from urllib.parse import urlparse
  4. from urllib.request import urlopen
  5. import sys
  6. class StatusLine(object):
  7. def __init__(self, out):
  8. self._out = out
  9. def __call__(self, message, progress = False):
  10. raise NotImplementedError
  11. class InteractiveStatusLine(StatusLine):
  12. __length = 0
  13. def __call__(self, message, progress = False):
  14. self._out.write(('\r%-' + str(self.__length) + 's') % message)
  15. self.__length = max(self.__length, len(message))
  16. class NoninteractiveStatusLine(StatusLine):
  17. def __call__(self, message, progress = False):
  18. if not progress:
  19. self._out.write(message + '\n')
  20. def createStatusLine(out):
  21. if out.isatty():
  22. return InteractiveStatusLine(out)
  23. else:
  24. return NoninteractiveStatusLine(out)
  25. def downloadURL(url, localDir):
  26. if not isdir(localDir):
  27. raise OSError('Local directory "%s" does not exist' % localDir)
  28. fileName = basename(urlparse(url).path)
  29. localPath = joinpath(localDir, fileName)
  30. prefix = 'Downloading %s: ' % fileName
  31. statusLine = createStatusLine(sys.stdout)
  32. statusLine(prefix + 'contacting server...')
  33. def reportProgress(doneSize, totalSize):
  34. statusLine(prefix + (
  35. '%d/%d bytes (%1.1f%%)...' % (
  36. doneSize, totalSize, (100.0 * doneSize) / totalSize
  37. )
  38. if totalSize > 0 else
  39. '%d bytes...' % doneSize
  40. ), True)
  41. try:
  42. try:
  43. with urlopen(url) as inp:
  44. totalSize = int(inp.info().get('Content-Length', '0'))
  45. with open(localPath, 'wb') as out:
  46. doneSize = 0
  47. reportProgress(doneSize, totalSize)
  48. while True:
  49. data = inp.read(16384)
  50. if not data:
  51. break
  52. out.write(data)
  53. doneSize += len(data)
  54. reportProgress(doneSize, totalSize)
  55. except OSError:
  56. statusLine(prefix + 'FAILED.')
  57. raise
  58. else:
  59. statusLine(prefix + 'done.')
  60. finally:
  61. print()
  62. except:
  63. if isfile(localPath):
  64. statusLine(prefix + 'removing partial download.')
  65. remove(localPath)
  66. raise
  67. if __name__ == '__main__':
  68. if len(sys.argv) == 3:
  69. try:
  70. downloadURL(*sys.argv[1 : ])
  71. except OSError as ex:
  72. print(ex, file=sys.stderr)
  73. sys.exit(1)
  74. else:
  75. print('Usage: python3 download.py url localdir', file=sys.stderr)
  76. sys.exit(2)