diff --git a/ramalama/common.py b/ramalama/common.py index 7f4f2e4e..62dfe890 100644 --- a/ramalama/common.py +++ b/ramalama/common.py @@ -1,17 +1,38 @@ -"""ramalama common module.""" +""" +MIT License +(C) 2024-2025 ramalama developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +""" import glob import hashlib import os import random +import logging import shutil import string import subprocess +import time import sys import urllib.request import urllib.error from ramalama.http_client import HttpClient + +logging.basicConfig( + level=logging.WARNING, + format="%(asctime)s - %(levelname)s - %(message)s" +) + MNT_DIR = "/mnt/models" MNT_FILE = f"{MNT_DIR}/model.file" @@ -165,25 +186,67 @@ def download_file(url, dest_path, headers=None, show_progress=True): headers (dict): Optional headers to include in the request. show_progress (bool): Whether to show a progress bar during download. - Returns: - None + Raises: + RuntimeError: If the download fails after multiple attempts. """ - http_client = HttpClient() - headers = headers or {} - # if we are not a tty, don't show progress, can pollute CI output and such + # If not running in a TTY, disable progress to prevent CI pollution if not sys.stdout.isatty(): show_progress = False - try: - http_client.init(url=url, headers=headers, output_file=dest_path, progress=show_progress) - except urllib.error.HTTPError as e: - if e.code == 416: # Range not satisfiable - if show_progress: - print(f"File {url} already fully downloaded.") - else: - raise e + http_client = HttpClient() + max_retries = 5 # Stop after 5 failures + retries = 0 + + while retries < max_retries: + try: + # Initialize HTTP client for the request + http_client.init(url=url, headers=headers, output_file=dest_path, progress=show_progress) + logging.info(f"✅ Successfully downloaded {url} to {dest_path}.") + return # Exit function if successful + + except urllib.error.HTTPError as e: + if e.code == 416: # "Range Not Satisfiable" error (file already downloaded) + if show_progress: + logging.info(f"✅ File {url} is already fully downloaded.") + return # No need to retry + + logging.error(f"❌ HTTP Error {e.code}: {e.reason}") + retries += 1 + + except urllib.error.URLError as e: + logging.error(f"❌ Network Error: {e.reason}") + retries += 1 + + except TimeoutError: + retries += 1 + logging.warning(f"⚠️ TimeoutError: The server took too long to respond. Retrying {retries}/{max_retries}...") + + except RuntimeError as e: # Catch network-related errors from HttpClient + retries += 1 + logging.warning(f"⚠️ {e}. Retrying {retries}/{max_retries}...") + + except IOError as e: + retries += 1 + logging.warning(f"⚠️ I/O Error: {e}. Retrying {retries}/{max_retries}...") + + except Exception as e: + logging.error(f"🚨 Unexpected error: {str(e)}") + raise + + if retries >= max_retries: + error_message = ( + "\n❌ Download failed after multiple attempts.\n" + "Possible causes:\n" + "- Internet connection issue\n" + "- Server is down or unresponsive\n" + "- Firewall or proxy blocking the request\n" + ) + logging.error(error_message) + sys.exit(1) + + time.sleep(2 ** retries * 0.1) # Exponential backoff (0.1s, 0.2s, 0.4s...) def engine_version(engine):