Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion filament_usage_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def _retrieve_model(self, model_url: str | None) -> str | None:
download3mfFromLocalFilesystem(uri.path, model_file)
else:
log(f"[filament-tracker] Downloading model via FTP: {model_url}")
download3mfFromFTP(model_url.replace("ftp://", "").replace(".gcode", ""), model_file)
download3mfFromFTP(model_url.rpartition('/')[-1], model_file) # Pull just filename to clear out any unexpected paths
return model_file.name
except Exception as exc:
log(f"Failed to fetch model: {exc}")
Expand Down
97 changes: 73 additions & 24 deletions tools_3mf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import re
import time
import io
from datetime import datetime
from config import PRINTER_CODE, PRINTER_IP
from urllib.parse import urlparse
Expand Down Expand Up @@ -72,34 +73,82 @@ def download3mfFromFTP(filename, destFile):
remote_path = "/cache/" + filename
local_path = destFile.name # 🔹 Download into the current directory
encoded_remote_path = urllib.parse.quote(remote_path)
with open(local_path, "wb") as f:
c = pycurl.Curl()
url = f"ftps://{ftp_host}{encoded_remote_path}"

# 🔹 Setup explicit FTPS connection (like FileZilla)
c.setopt(c.URL, url)
c.setopt(c.USERPWD, f"{ftp_user}:{ftp_pass}")
c.setopt(c.WRITEDATA, f)

# 🔹 Enable SSL/TLS
c.setopt(c.SSL_VERIFYPEER, 0) # Disable SSL verification
c.setopt(c.SSL_VERIFYHOST, 0)

# 🔹 Enable passive mode (like FileZilla)
c.setopt(c.FTP_SSL, c.FTPSSL_ALL)

# 🔹 Enable proper TLS authentication
c.setopt(c.FTPSSLAUTH, c.FTPAUTH_TLS)

url = f"ftps://{ftp_host}{encoded_remote_path}"

log("[DEBUG] Starting file download...")


try:
log(f"[DEBUG] Attempting file download of: {remote_path}") #Log attempted path

# Setup a retry loop
# Try to prevent race condition where trying to access file before it is fully in cache, causing File not found errors
max_retries = 3
for attempt in range(1, max_retries + 1):
with open(local_path, "wb") as f:
c = setupPycurlConnection(ftp_user, ftp_pass)
try:
c.setopt(c.URL, url)
# Set output to file
c.setopt(c.WRITEDATA, f)
log(f"[DEBUG] Attempt {attempt}: Starting download of {filename}...")

# Perform the transfer
c.perform()

log("[DEBUG] File successfully downloaded!")
except pycurl.error as e:
log(f"[ERROR] cURL error: {e}")
c.close()
return True # Exit function on success

# Error, check its just a file not found error before retry
except pycurl.error as e:
err_code = e.args[0]
c.close()
if err_code == 78: # File Not Found
if attempt < max_retries:
log(f"[WARNING] File not found. Printer might still be writing. Retrying in 1s...")
time.sleep(2)
continue
else:
log("[ERROR] File not found after max retries.")
log("[DEBUG] Listing found printer files in /cache directory")
buffer = io.BytesIO()
c = setupPycurlConnection(ftp_user, ftp_pass)
c.setopt(c.URL, f"ftps://{ftp_host}/cache/")
c.setopt(c.WRITEDATA, buffer)
c.setopt(c.DIRLISTONLY, True)
try:
c.perform()
log(f"[DEBUG] Directory Listing: {buffer.getvalue().decode('utf-8').splitlines()}")
except:
log("[ERROR] Could not retrieve directory listing.")
# Check if external storage not setup or connected. /cache is denied access
if err_code == 9: # Server denied you to change to the given directory
log("[DEBUG] Printer denied access to /cache path. Ensure external storage is setup to store print files in printer settings.")
break
else:
log(f"[ERROR] Fatal cURL error {err_code}: {e}")
break # Don't retry for non-78 File Not Found errors

def setupPycurlConnection(ftp_user, ftp_pass):
# Setup shared options for curl connections
c = pycurl.Curl()

# 🔹 Setup explicit FTPS connection (like FileZilla)

c.setopt(c.USERPWD, f"{ftp_user}:{ftp_pass}")


# 🔹 Enable SSL/TLS
c.setopt(c.SSL_VERIFYPEER, 0) # Disable SSL verification
c.setopt(c.SSL_VERIFYHOST, 0)

# 🔹 Enable passive mode (like FileZilla)
c.setopt(c.FTP_SSL, c.FTPSSL_ALL)

# 🔹 Enable proper TLS authentication
c.setopt(c.FTPSSLAUTH, c.FTPAUTH_TLS)

c.close()
return c

def download3mfFromLocalFilesystem(path, destFile):
with open(path, "rb") as src_file:
Expand Down Expand Up @@ -127,7 +176,7 @@ def getMetaDataFrom3mf(url):
elif url.startswith("local:"):
download3mfFromLocalFilesystem(url.replace("local:", ""), temp_file)
else:
download3mfFromFTP(url.replace("ftp://", "").replace(".gcode",""), temp_file)
download3mfFromFTP(url.rpartition('/')[-1], temp_file) # Pull just filename to clear out any unexpected paths

temp_file.close()
metadata["model_path"] = url
Expand Down