diff --git a/backend/app/services/bambu_ftp.py b/backend/app/services/bambu_ftp.py index 66f4b548f..f3dfd09b4 100644 --- a/backend/app/services/bambu_ftp.py +++ b/backend/app/services/bambu_ftp.py @@ -80,6 +80,9 @@ class BambuFTPClient: # Models that may need SSL mode fallback (try prot_p first, fall back to prot_c) # These models have varying FTP SSL behavior depending on firmware version A1_MODELS = ("A1", "A1 Mini") + # Chunk size for manual upload transfer (1MB) + # Larger chunks reduce overhead and work better with A1 printers + CHUNK_SIZE = 1024 * 1024 # Cache for working FTP modes per printer IP # Maps IP -> "prot_p" or "prot_c" @@ -363,15 +366,37 @@ class BambuFTPClient: uploaded = 0 - def on_block(block: bytes): - nonlocal uploaded - uploaded += len(block) - if progress_callback: - progress_callback(uploaded, file_size) - + # Use manual transfer instead of storbinary() for A1 compatibility + # A1 printers have issues with storbinary's voidresp() hanging after transfer with open(local_path, "rb") as f: logger.debug(f"FTP STOR command starting for {remote_path}") - self._ftp.storbinary(f"STOR {remote_path}", f, callback=on_block) + conn = self._ftp.transfercmd(f"STOR {remote_path}") + + # Set explicit socket options for reliable transfer + conn.setblocking(True) + conn.settimeout(120) # 2 minute timeout per chunk + + try: + while True: + chunk = f.read(self.CHUNK_SIZE) + if not chunk: + logger.debug("FTP upload: final chunk reached") + break + + conn.sendall(chunk) + uploaded += len(chunk) + logger.debug(f"FTP upload progress: {uploaded}/{file_size} bytes") + + if progress_callback: + progress_callback(uploaded, file_size) + + except OSError as e: + logger.error(f"FTP connection lost during upload: {e}") + conn.close() + raise + + conn.close() + logger.info(f"FTP upload complete: {remote_path}") return True except ftplib.error_perm as e: @@ -399,8 +424,24 @@ class BambuFTPClient: return False try: - buffer = BytesIO(data) - self._ftp.storbinary(f"STOR {remote_path}", buffer) + # Use manual transfer instead of storbinary() for A1 compatibility + conn = self._ftp.transfercmd(f"STOR {remote_path}") + conn.setblocking(True) + conn.settimeout(120) + + try: + # Send data in chunks + offset = 0 + while offset < len(data): + chunk = data[offset : offset + self.CHUNK_SIZE] + conn.sendall(chunk) + offset += len(chunk) + except OSError as e: + logger.error(f"FTP connection lost during upload_bytes: {e}") + conn.close() + raise + + conn.close() return True except Exception: return False