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