Files
happy-life-star/course-web/download_resources_v2.py
T
2025-12-21 16:57:54 +08:00

132 lines
4.6 KiB
Python

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()