132 lines
4.6 KiB
Python
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()
|