import os import urllib.request import urllib.error import hashlib import sys import ssl import time # Configuration - PncyssD Resource Manifest RESOURCES = [ { "url": "https://grainy-gradients.vercel.app/noise.svg", "filename": "noise.svg", "dir": "src/assets", "md5": None # Skip check for dynamic/external resources if hash unknown, or fill if known } ] def get_remote_file_size(url): try: req = urllib.request.Request(url, method='HEAD', headers={'User-Agent': 'Mozilla/5.0'}) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE with urllib.request.urlopen(req, context=ctx, timeout=10) as response: return int(response.headers.get('Content-Length', 0)) except: return 0 def calculate_md5(filepath): hash_md5 = hashlib.md5() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def download_file_with_resume(url, directory, filename, expected_md5=None): if not os.path.exists(directory): os.makedirs(directory) filepath = os.path.join(directory, filename) temp_filepath = filepath + ".part" total_size = get_remote_file_size(url) downloaded_size = 0 if os.path.exists(temp_filepath): downloaded_size = os.path.getsize(temp_filepath) # Check if file already exists and is complete if os.path.exists(filepath): if expected_md5: current_md5 = calculate_md5(filepath) if current_md5 == expected_md5: print(f"[SKIP] {filename} already exists and matches MD5.") return True else: print(f"[WARN] {filename} exists but MD5 mismatch. Re-downloading.") else: # If no MD5 provided, check size if possible, or just skip if it exists if total_size > 0 and os.path.getsize(filepath) == total_size: print(f"[SKIP] {filename} already exists (size match).") return True headers = {'User-Agent': 'Mozilla/5.0'} if downloaded_size > 0: headers['Range'] = f'bytes={downloaded_size}-' print(f"[RESUME] Resuming {filename} from {downloaded_size} bytes...") else: print(f"[START] Downloading {filename}...") req = urllib.request.Request(url, headers=headers) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE try: with urllib.request.urlopen(req, context=ctx, timeout=20) as response: mode = 'ab' if downloaded_size > 0 else 'wb' with open(temp_filepath, mode) as f: while True: chunk = response.read(8192) if not chunk: break f.write(chunk) downloaded_size += len(chunk) # Simple progress bar if total_size > 0: percent = (downloaded_size / total_size) * 100 sys.stdout.write(f"\rProgress: [{('=' * int(percent // 2)).ljust(50)}] {percent:.1f}%") sys.stdout.flush() print() # Newline after progress # Verify MD5 if provided if expected_md5: if calculate_md5(temp_filepath) != expected_md5: print(f"\n[ERROR] MD5 verification failed for {filename}") return False os.rename(temp_filepath, filepath) print(f"[SUCCESS] Saved to {filepath}") return True except urllib.error.HTTPError as e: if e.code == 416: # Range Not Satisfiable (likely already complete) print(f"\n[INFO] File likely already complete.") if os.path.exists(temp_filepath): os.rename(temp_filepath, filepath) return True print(f"\n[ERROR] HTTP Error: {e}") return False except Exception as e: print(f"\n[ERROR] Failed to download {url}: {e}") return False def main(): print("=== PncyssD Static Resource Downloader ===") print("Features: MD5 Check, Resume Capability, Progress Bar") print("==========================================") success_count = 0 for res in RESOURCES: if download_file_with_resume(res["url"], res["dir"], res["filename"], res.get("md5")): success_count += 1 print(f"\nTotal: {len(RESOURCES)}, Success: {success_count}") if __name__ == "__main__": main()