Init
im going to bed -=-
This commit is contained in:
190
lib/ftp.py
Normal file
190
lib/ftp.py
Normal file
@@ -0,0 +1,190 @@
|
||||
import os
|
||||
from ftplib import FTP
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn
|
||||
import lib.paramiko as paramiko
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
filename="sftp_debug.log",
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
class SFTPSync:
|
||||
def __init__(self, host, user, password, port=22):
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.port = int(port)
|
||||
self.client = None
|
||||
self.sftp = None
|
||||
|
||||
def log(self, message):
|
||||
"""Helper to log to file and console if needed"""
|
||||
logging.info(message)
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.log(f"Initiating SSH connection to {self.host}:{self.port}")
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
self.log(f"Attempting login for user: {self.user}")
|
||||
self.client.connect(
|
||||
self.host,
|
||||
port=self.port,
|
||||
username=self.user,
|
||||
password=self.password,
|
||||
timeout=15,
|
||||
allow_agent=False, # Prevents interference from local SSH agents
|
||||
look_for_keys=False
|
||||
)
|
||||
|
||||
self.log("SSH connection established. Opening SFTP session...")
|
||||
self.sftp = self.client.open_sftp()
|
||||
self.log("SFTP session opened successfully.")
|
||||
return True
|
||||
except paramiko.AuthenticationException:
|
||||
self.log("Authentication failed: Check username or password.")
|
||||
return "Auth failed: Invalid credentials."
|
||||
except paramiko.SSHException as e:
|
||||
self.log(f"SSH protocol error: {e}")
|
||||
return f"SSH Error: {e}"
|
||||
except Exception as e:
|
||||
self.log(f"Unexpected connection error: {e}")
|
||||
return f"Error: {e}"
|
||||
|
||||
def upload_with_progress(self, local_path, remote_dir):
|
||||
if not os.path.exists(local_path):
|
||||
self.log(f"Local error: File {local_path} not found.")
|
||||
return "Local file not found."
|
||||
|
||||
file_size = os.path.getsize(local_path)
|
||||
filename = os.path.basename(local_path)
|
||||
# Ensure remote path uses forward slashes for Linux servers
|
||||
remote_path = (Path(remote_dir) / filename).as_posix()
|
||||
|
||||
self.log(f"Starting upload: {local_path} -> {remote_path} ({file_size} bytes)")
|
||||
|
||||
try:
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
DownloadColumn(),
|
||||
TransferSpeedColumn(),
|
||||
) as progress:
|
||||
|
||||
task = progress.add_task(f"Uploading...", total=file_size)
|
||||
|
||||
def callback(transferred, total):
|
||||
progress.update(task, completed=transferred)
|
||||
# Log every 25% to avoid bloating the log file
|
||||
if transferred % (total // 4 + 1) < 8192:
|
||||
logging.debug(f"Progress: {transferred}/{total} bytes")
|
||||
|
||||
self.sftp.put(local_path, remote_path, callback=callback)
|
||||
|
||||
self.log(f"Upload completed successfully: {filename}")
|
||||
return True
|
||||
except PermissionError:
|
||||
self.log(f"Permission denied on server: Cannot write to {remote_dir}")
|
||||
return "Server Error: Permission denied."
|
||||
except Exception as e:
|
||||
self.log(f"Upload failed mid-transfer: {e}")
|
||||
return f"Upload error: {e}"
|
||||
def upload_directory(self, local_dir, remote_root):
|
||||
"""Recursively uploads a directory, ensuring empty folders are created."""
|
||||
self.log(f"Scanning directory: {local_dir}")
|
||||
|
||||
for root, dirs, files in os.walk(local_dir):
|
||||
rel_path = os.path.relpath(root, local_dir)
|
||||
|
||||
# Build the remote path
|
||||
if rel_path == ".":
|
||||
# This is the root folder itself (e.g., 'core')
|
||||
remote_dir = (Path(remote_root) / Path(local_dir).name).as_posix()
|
||||
else:
|
||||
# These are subfolders (e.g., 'core/utils')
|
||||
remote_dir = (Path(remote_root) / Path(local_dir).name / rel_path).as_posix()
|
||||
|
||||
# --- THE FIX: Create folder even if 'files' is empty ---
|
||||
try:
|
||||
self.sftp.mkdir(remote_dir)
|
||||
self.log(f"Created remote folder: {remote_dir}")
|
||||
print(f"Created: {remote_dir}") # Feedback for the user
|
||||
except IOError:
|
||||
# Folder likely exists
|
||||
self.log(f"Folder already exists: {remote_dir}")
|
||||
|
||||
# Now upload files if there are any
|
||||
for filename in files:
|
||||
local_file = os.path.join(root, filename)
|
||||
self.upload_with_progress(local_file, remote_dir)
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
self.log("Closing SFTP and SSH connections.")
|
||||
if self.sftp: self.sftp.close()
|
||||
if self.client: self.client.close()
|
||||
|
||||
|
||||
|
||||
class FTPSync:
|
||||
def __init__(self, host, user, password, port=21):
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.port = int(port)
|
||||
self.ftp = FTP()
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.ftp.connect(self.host, self.port, timeout=10)
|
||||
self.ftp.login(self.user, self.password)
|
||||
self.ftp.set_pasv(True) # Passive mode is safer for most firewalls
|
||||
return True
|
||||
except Exception as e:
|
||||
return f"Connection error: {e}"
|
||||
|
||||
def upload_with_progress(self, local_path, remote_dir):
|
||||
if not os.path.exists(local_path):
|
||||
return "Local file not found."
|
||||
|
||||
file_size = os.path.getsize(local_path)
|
||||
filename = os.path.basename(local_path)
|
||||
|
||||
try:
|
||||
self.ftp.cwd(remote_dir)
|
||||
|
||||
# Define the Rich Progress Bar layout
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
DownloadColumn(),
|
||||
TransferSpeedColumn(),
|
||||
) as progress:
|
||||
|
||||
task = progress.add_task(f"Uploading {filename}...", total=file_size)
|
||||
|
||||
with open(local_path, "rb") as f:
|
||||
# Callback function called every time a chunk is sent
|
||||
def callback(chunk):
|
||||
progress.update(task, advance=len(chunk))
|
||||
|
||||
# 8KB is a standard buffer size for FTP
|
||||
self.ftp.storbinary(f"STOR {filename}", f, blocksize=8192, callback=callback)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
return f"Upload failed: {e}"
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.ftp.quit()
|
||||
except:
|
||||
self.ftp.close()
|
||||
Reference in New Issue
Block a user