diff --git a/README.rst b/README.rst index 59589f97..139de6c4 100644 --- a/README.rst +++ b/README.rst @@ -848,7 +848,7 @@ is being saved to a file. Vary: Accept-Encoding Downloading 494.89 kB to "jkbr-httpie-0.4.1-33-gfc4f70a.tar.gz" - / 104.00 kB 21.01% 47.55 kB/s 0:00:08 ETA + / 21.01% 104.00 kB 47.55 kB/s 0:00:08 ETA If not provided via ``--output, -o``, the output filename will be determined @@ -1257,6 +1257,5 @@ Changelog .. _0.4.0: https://github.com/jkbr/httpie/compare/0.3.0...0.4.0 .. _0.4.1: https://github.com/jkbr/httpie/compare/0.4.0...0.4.1 .. _0.5.0-alpha: https://github.com/jkbr/httpie/compare/0.4.0...master -.. _stable version: https://github.com/jkbr/httpie/tree/0.3.0#readme .. _AUTHORS.rst: https://github.com/jkbr/httpie/blob/master/AUTHORS.rst .. _LICENSE: https://github.com/jkbr/httpie/blob/master/LICENSE diff --git a/httpie/downloads.py b/httpie/downloads.py index df7382db..b63611c2 100644 --- a/httpie/downloads.py +++ b/httpie/downloads.py @@ -22,8 +22,8 @@ PARTIAL_CONTENT = 206 CLEAR_LINE = '\r\033[K' PROGRESS = ( - '{downloaded: >10}' - ' {percentage: 6.2f}%' + '{percentage: 6.2f}%' + ' {downloaded: >10}' ' {speed: >10}/s' ' {eta: >8} ETA' ) @@ -48,6 +48,9 @@ def parse_content_range(content_range, resumed_from): :return: total size of the response body when fully downloaded. """ + if content_range is None: + raise ContentRangeError('Missing Content-Range') + pattern = ( '^bytes (?P\d+)-(?P\d+)' '/(\*|(?P\d+))$' @@ -203,10 +206,10 @@ class Download(object): if self._output_file: if self._resume and response.status_code == PARTIAL_CONTENT: - content_range = response.headers.get('Content-Range') - if content_range: - total_size = parse_content_range( - content_range, self._resumed_from) + total_size = parse_content_range( + response.headers.get('Content-Range'), + self._resumed_from + ) else: self._resumed_from = 0 @@ -264,7 +267,7 @@ class Download(object): @property def interrupted(self): return ( - self._output_file.closed + self._finished and self._progress.total_size and self._progress.total_size != self._progress.downloaded ) @@ -321,13 +324,12 @@ class ProgressReporter(object): """ self.progress = progress self.output = output - self._prev_bytes = 0 - self._prev_time = time() - self._spinner_pos = 0 self._tick = tick self._update_interval = update_interval + self._spinner_pos = 0 self._status_line = '' - super(ProgressReporter, self).__init__() + self._prev_bytes = 0 + self._prev_time = time() def report(self): if self.progress.has_finished: @@ -389,15 +391,13 @@ class ProgressReporter(object): ) self.output.flush() - self._spinner_pos = ( - self._spinner_pos + 1 - if self._spinner_pos + 1 != len(SPINNER) - else 0 - ) + self._spinner_pos = (self._spinner_pos + 1 + if self._spinner_pos + 1 != len(SPINNER) + else 0) def sum_up(self): - actually_downloaded = ( - self.progress.downloaded - self.progress.resumed_from) + actually_downloaded = (self.progress.downloaded + - self.progress.resumed_from) time_taken = self.progress.time_finished - self.progress.time_started self.output.write(CLEAR_LINE) diff --git a/httpie/utils.py b/httpie/utils.py index 811417a7..fe721c4e 100644 --- a/httpie/utils.py +++ b/httpie/utils.py @@ -37,7 +37,7 @@ def humanize_bytes(n, precision=2): ] if n == 1: - return '1 byte' + return '1 B' for factor, suffix in abbrevs: if n >= factor: diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..bb39dd92 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +httpbin +docutils diff --git a/tests/tests.py b/tests/tests.py index b99a5f91..e56fb32d 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -19,6 +19,7 @@ To make it run faster and offline you can:: HTTPBIN_URL=http://localhost:5000 tox """ +from io import BytesIO import subprocess import os import sys @@ -28,6 +29,7 @@ import tempfile import unittest import shutil import time +from requests.structures import CaseInsensitiveDict try: from urllib.request import urlopen @@ -71,6 +73,7 @@ from httpie.downloads import ( filename_from_url, get_unique_filename, ContentRangeError, + Download, ) @@ -1452,6 +1455,9 @@ class DownloadUtilsTest(BaseTestCase): self.assertEqual(parse('bytes 100-199/200', 100), 200) self.assertEqual(parse('bytes 100-199/*', 100), 200) + # missing + self.assertRaises(ContentRangeError, parse, None, 100) + # syntax error self.assertRaises(ContentRangeError, parse, 'beers 100-199/*', 100) @@ -1525,10 +1531,19 @@ class DownloadUtilsTest(BaseTestCase): ) +class Response(object): + + # noinspection PyDefaultArgument + def __init__(self, url, headers={}, status_code=200): + self.url = url + self.headers = CaseInsensitiveDict(headers) + self.status_code = status_code + + class DownloadTest(BaseTestCase): # TODO: Download tests. - def test_download(self): + def test_actual_download(self): url = httpbin('/robots.txt') body = urlopen(url).read().decode() r = http( @@ -1544,6 +1559,37 @@ class DownloadTest(BaseTestCase): self.assertIn('Done', r.stderr) self.assertEqual(body, r) + def test_download_no_Content_Length(self): + download = Download(output_file=open(os.devnull, 'w')) + download.start(Response(url=httpbin('/'))) + download._on_progress(b'12345') + download.finish() + self.assertFalse(download.interrupted) + + def test_download_with_Content_Length(self): + download = Download( + output_file=open(os.devnull, 'w'), + progress_file=BytesIO(), + ) + download.start(Response( + url=httpbin('/'), + headers={'Content-Length': 5} + )) + download._on_progress(b'12345') + time.sleep(1.5) + download.finish() + self.assertFalse(download.interrupted) + + def test_download_interrupted(self): + download = Download(output_file=open(os.devnull, 'w')) + download.start(Response( + url=httpbin('/'), + headers={'Content-Length': 5} + )) + download._on_progress(b'1234') + download.finish() + self.assertTrue(download.interrupted) + if __name__ == '__main__': unittest.main()