Files
canti/scratch/deploy_ftp.py
T
2026-06-17 01:04:27 +02:00

155 lines
5.3 KiB
Python

import os
import sys
import threading
from ftplib import FTP, error_perm
from concurrent.futures import ThreadPoolExecutor, as_completed
thread_local = threading.local()
def load_env():
env = {}
if os.path.exists('.env'):
with open('.env', 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
if '=' in line:
key, val = line.split('=', 1)
env[key.strip()] = val.strip()
return env
def get_ftp_connection(ftp_host, ftp_user, ftp_pass):
if not hasattr(thread_local, "ftp") or thread_local.ftp is None:
ftp = FTP(ftp_host, timeout=30)
ftp.login(ftp_user, ftp_pass)
ftp.passive = True
thread_local.ftp = ftp
return thread_local.ftp
def close_thread_connection():
if hasattr(thread_local, "ftp") and thread_local.ftp is not None:
try:
thread_local.ftp.quit()
except:
try:
thread_local.ftp.close()
except:
pass
thread_local.ftp = None
def ensure_remote_dir(ftp, path):
parts = path.replace('\\', '/').strip('/').split('/')
current = ""
for part in parts:
if not part:
continue
current += "/" + part
try:
ftp.mkd(current)
print(f"\nCreated remote dir: {current}")
except error_perm:
# Directory already exists or permission error (which is normal if it exists)
pass
def upload_file_task(local_path, rel_path, ftp_host, ftp_user, ftp_pass, created_dirs, created_dirs_lock):
ftp_path = rel_path.replace('\\', '/')
remote_dir = os.path.dirname(ftp_path)
# Ensure remote directory exists
if remote_dir:
with created_dirs_lock:
if remote_dir not in created_dirs:
try:
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
ensure_remote_dir(ftp, remote_dir)
created_dirs.add(remote_dir)
except Exception as e:
thread_local.ftp = None
try:
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
ensure_remote_dir(ftp, remote_dir)
created_dirs.add(remote_dir)
except Exception as err2:
return False, ftp_path, f"Failed directory creation: {err2}"
# Upload the file
for attempt in range(3):
try:
ftp = get_ftp_connection(ftp_host, ftp_user, ftp_pass)
with open(local_path, 'rb') as f:
ftp.storbinary(f"STOR {ftp_path}", f)
return True, ftp_path, None
except Exception as e:
thread_local.ftp = None # Force reconnect on next attempt
if attempt == 2:
return False, ftp_path, str(e)
return False, ftp_path, "Unknown error"
def main():
env = load_env()
ftp_host = "ftp.canticristiani.it"
ftp_user = "canticristiani.it"
ftp_pass = env.get("FTP_PASSWORD")
if not ftp_pass:
print("❌ Error: FTP_PASSWORD not found in .env")
sys.exit(1)
local_dir = "www"
if not os.path.isdir(local_dir):
print(f"❌ Error: Local dir '{local_dir}' not found.")
sys.exit(1)
files_to_upload = []
for root, dirs, files in os.walk(local_dir):
for file in files:
local_path = os.path.join(root, file)
rel_path = os.path.relpath(local_path, local_dir)
files_to_upload.append((local_path, rel_path))
total = len(files_to_upload)
uploaded = 0
errors = []
created_dirs = set()
created_dirs_lock = threading.Lock()
progress_lock = threading.Lock()
# Thread limit: default to 100 as requested
num_threads = int(os.environ.get("FTP_THREADS", env.get("FTP_THREADS", "100")))
print(f"🚀 Starting parallel upload of {total} files using {num_threads} threads...")
def run_task(item):
nonlocal uploaded
local_path, rel_path = item
success, ftp_path, err_msg = upload_file_task(
local_path, rel_path, ftp_host, ftp_user, ftp_pass, created_dirs, created_dirs_lock
)
with progress_lock:
uploaded += 1
print(f"\rUploading [{uploaded}/{total}] {ftp_path}...", end="", flush=True)
if not success:
errors.append((ftp_path, err_msg))
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(run_task, item) for item in files_to_upload]
for future in as_completed(futures):
pass
# Cleanup connections
with ThreadPoolExecutor(max_workers=num_threads) as cleanup_executor:
cleanups = [cleanup_executor.submit(close_thread_connection) for _ in range(num_threads)]
for future in as_completed(cleanups):
pass
if errors:
print(f"\n⚠️ Deploy completed with {len(errors)} errors:")
for path, err in errors:
print(f" {path}: {err}")
sys.exit(1)
else:
print("\n\n✅ Deploy completed successfully with 100% files uploaded without errors!")
if __name__ == '__main__':
main()