Repository: bitcoin/bitcoin
File: test/get_previous_releases.py (Python)

Code before:
def download_binary(tag, args) -> int:
    if Path(tag).is_dir():
        if not args.remove_dir:
            print('Using cached {}'.format(tag))
            return 0
        shutil.rmtree(tag)
    Path(tag).mkdir()
    bin_path = 'bin/bitcoin-core-{}'.format(tag[1:])
    match = re.compile('v(.*)(rc[0-9]+)$').search(tag)
    if match:
        bin_path = 'bin/bitcoin-core-{}/test.{}'.format(
            match.group(1), match.group(2))
    platform = args.platform
    if tag < "v23" and platform in ["x86_64-apple-darwin", "arm64-apple-darwin"]:
        platform = "osx64"
    tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format(
        tag=tag[1:], platform=platform)
    tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format(
        bin_path=bin_path, tarball=tarball)

    print('Fetching: {tarballUrl}'.format(tarballUrl=tarballUrl))

    ret = subprocess.run(['curl', '--fail', '--remote-name', tarballUrl]).returncode
    if ret:
        return ret

    hasher = hashlib.sha256()
    with open(tarball, "rb") as afile:
        hasher.update(afile.read())
    tarballHash = hasher.hexdigest()

    if tarballHash not in SHA256_SUMS or SHA256_SUMS[tarballHash]['tarball'] != tarball:
        if tarball in [v['tarball'] for v in SHA256_SUMS.values()]:
            print("Checksum did not match")
            return 1

        print("Checksum for given version doesn't exist")
        return 1
    print("Checksum matched")

    # Extract tarball
    ret = subprocess.run(['tar', '-zxf', tarball, '-C', tag,
                          '--strip-components=1',
                          'bitcoin-{tag}'.format(tag=tag[1:])]).returncode
    if ret != 0:
        print(f"Failed to extract the {tag} tarball")
        return ret

    Path(tarball).unlink()

    if tag >= "v23" and platform == "arm64-apple-darwin":

Review comment (bug):
`urllib.request.urlretrieve` is marked as being part of the legacy interface: https://docs.python.org/3/library/urllib.request.html#legacy-interface. Would be good to avoid introducing new code using it.

Here an alternative that is unfortunately a bit more verbose:
```diff
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index 955fcf15d1..7e5c66de0f 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -106,19 +106,36 @@ def pushd(new_dir) -> None:

 def download_from_url(url, archive):
     last_print_time = time.time()
-    def progress_hook(count, block_size, total_size):
+    def progress_hook(progress_bytes, total_size):
         nonlocal last_print_time
         now = time.time()
-        percent = min(100, (count * block_size * 100) / total_size)
+        percent = min(100, (progress_bytes * 100) / total_size)
         bar_length = 40
         filled_length = int(bar_length * percent / 100)
-        bar = '█' * filled_length + '-' * (bar_length - filled_length)
+        bar = '#' * filled_length + '-' * (bar_length - filled_length)
         if now - last_print_time >= 1 or percent >= 100:
             sys.stdout.write(f'\rDownloading: [{bar}] {percent:.1f}%')
             sys.stdout.flush()
             last_print_time = now
-    urllib.request.urlretrieve(url, archive, reporthook=progress_hook)
+
+    with urllib.request.urlopen(url) as response:
+        if response.status != 200:
+            raise RuntimeError(f"HTTP request failed with status code: {response.status}")
+
+        total_size = int(response.getheader('Content-Length', 0))
+        progress_bytes = 0
+
+        with open(archive, 'wb') as file:
+            while True:
+                chunk = response.read(8192)
+                if not chunk:
+                    break
+                file.write(chunk)
+                progress_bytes += len(chunk)
+                progress_hook(progress_bytes, total_size)
+
     print('\n', end="")
+    sys.stdout.flush() # Flush to avoid error output on the same line.

 def download_binary(tag, args) -> int:
```

Edit: Avoided use of http.client & urllib.parse in favor of smaller change.

Code after:


@contextlib.contextmanager
def pushd(new_dir) -> None:
    previous_dir = os.getcwd()
    os.chdir(new_dir)
    try:
        yield
    finally:
        os.chdir(previous_dir)


def download_from_url(url, archive):
    last_print_time = time.time()

    def progress_hook(progress_bytes, total_size):
        nonlocal last_print_time
        now = time.time()
        percent = min(100, (progress_bytes * 100) / total_size)
        bar_length = 40
        filled_length = int(bar_length * percent / 100)
        bar = '#' * filled_length + '-' * (bar_length - filled_length)
        if now - last_print_time >= 1 or percent >= 100:
            print(f'\rDownloading: [{bar}] {percent:.1f}%', flush=True, end="")
            last_print_time = now

    with urllib.request.urlopen(url) as response:
        if response.status != 200:
            raise RuntimeError(f"HTTP request failed with status code: {response.status}")

        total_size = int(response.getheader('Content-Length', 0))
        progress_bytes = 0

        with open(archive, 'wb') as file:
            while True:
                chunk = response.read(8192)
                if not chunk:
                    break
                file.write(chunk)
                progress_bytes += len(chunk)
                progress_hook(progress_bytes, total_size)

    print('\n', flush=True, end="") # Flush to avoid error output on the same line.


def download_binary(tag, args) -> int:
    if Path(tag).is_dir():
        if not args.remove_dir:
            print('Using cached {}'.format(tag))
            return 0
        shutil.rmtree(tag)