mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 19:36:28 +02:00
Fix critical FTP upload failure and revert dangerous exception narrowing
The CodeQL cleanup in "Housekeeping" (2b11efd) bulk-narrowed except
clauses across 50+ files, breaking FTP uploads on ALL printer models.
ftplib.error_perm (550 errors) is not a subclass of ftplib.error_reply,
so diagnose_storage() CWD failures escaped the handler and prevented
STOR from ever executing — causing 100% upload failure and HTTP 500s
on /api/v1/archives/{id}/reprint and /api/v1/library/files/{id}/print.
FTP fixes:
- Remove diagnose_storage() from upload hot path
- Change all except (OSError, ftplib.error_reply) to
except (OSError, ftplib.Error) across bambu_ftp.py
Exception handling reverts (9 files):
- Revert narrowed except clauses back to except Exception in route
handlers and service code where broad catches are intentional
defensive programming (archive parsing, HTTP clients, 3MF/ZIP
processing, Home Assistant, firmware checks)
- Keep narrow exceptions only where safe (single-op blocks like
int(), file.unlink(), socket.close())
- Remove unused XMLParseError imports from archive.py, threemf_tools.py
Version system:
- Add 4-segment version support (e.g. 0.1.8.1) for patch releases
- Bump version to 0.1.8.1
Closes #287
This commit is contained in:
+10
-1
@@ -2,7 +2,16 @@
|
||||
|
||||
All notable changes to Bambuddy will be documented in this file.
|
||||
|
||||
## [0.1.9] - Unrelased
|
||||
## [0.1.8.1] - 2026-02-07
|
||||
|
||||
### Fixed
|
||||
- **FTP Upload Broken on All Printer Models** — Fixed critical bug where all FTP uploads failed with "550 Failed to change directory":
|
||||
- `diagnose_storage()` was running before every upload, and its CWD failures (`ftplib.error_perm`) were not caught because `error_perm` is not a subclass of `error_reply`
|
||||
- Removed `diagnose_storage()` from the upload hot path
|
||||
- Changed all FTP exception handlers from `except (OSError, ftplib.error_reply)` to `except (OSError, ftplib.Error)` to catch all FTP error types
|
||||
- **HTTP 500 on Reprint and Print Endpoints** — Fixed 500 errors on `/api/v1/archives/{id}/reprint` and `/api/v1/library/files/{id}/print` caused by the FTP failure above
|
||||
- **Exception Handling Reverted** — Reverted overly-narrow exception handling introduced in 0.1.8 that could cause uncaught errors in archive parsing, HTTP clients, 3MF/ZIP processing, Home Assistant, and firmware checks
|
||||
- **4-Segment Version Support** — Version parser now supports patch releases like `0.1.8.1` for hotfixes without incrementing the minor version
|
||||
|
||||
## [0.1.8] - 2026-02-06
|
||||
|
||||
|
||||
@@ -71,31 +71,33 @@ def _find_executable(name: str) -> str | None:
|
||||
def parse_version(version: str) -> tuple:
|
||||
"""Parse version string into tuple for comparison.
|
||||
|
||||
Returns (major, minor, patch, is_prerelease, prerelease_num)
|
||||
Returns (major, minor, patch, micro, is_prerelease, prerelease_num)
|
||||
where is_prerelease is 0 for release, 1 for prerelease.
|
||||
This ensures releases sort higher than prereleases of same version.
|
||||
|
||||
Examples:
|
||||
"0.1.5" -> (0, 1, 5, 0, 0) # release
|
||||
"0.1.5b7" -> (0, 1, 5, 1, 7) # beta 7
|
||||
"0.1.5b10" -> (0, 1, 5, 1, 10) # beta 10
|
||||
"0.1.5" -> (0, 1, 5, 0, 0, 0) # release
|
||||
"0.1.5b7" -> (0, 1, 5, 0, 1, 7) # beta 7
|
||||
"0.1.5b10" -> (0, 1, 5, 0, 1, 10) # beta 10
|
||||
"0.1.8.1" -> (0, 1, 8, 1, 0, 0) # patch release
|
||||
"""
|
||||
# Remove 'v' prefix if present
|
||||
version = version.lstrip("v")
|
||||
|
||||
# Match version pattern: major.minor.patch[b|beta|alpha|rc]N
|
||||
match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:b|beta|alpha|rc)?(\d+)?", version)
|
||||
# Match version pattern: major.minor.patch[.micro][b|beta|alpha|rc]N
|
||||
match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:b|beta|alpha|rc)?(\d+)?", version)
|
||||
|
||||
if match:
|
||||
major = int(match.group(1))
|
||||
minor = int(match.group(2))
|
||||
patch = int(match.group(3))
|
||||
prerelease_num = int(match.group(4)) if match.group(4) else 0
|
||||
micro = int(match.group(4)) if match.group(4) else 0
|
||||
prerelease_num = int(match.group(5)) if match.group(5) else 0
|
||||
|
||||
# Check if this is a prerelease (has b/beta/alpha/rc suffix)
|
||||
is_prerelease = 1 if re.search(r"[a-zA-Z]", version.split(".")[-1]) else 0
|
||||
|
||||
return (major, minor, patch, is_prerelease, prerelease_num)
|
||||
return (major, minor, patch, micro, is_prerelease, prerelease_num)
|
||||
|
||||
# Fallback: try simple split
|
||||
parts = []
|
||||
@@ -106,7 +108,7 @@ def parse_version(version: str) -> tuple:
|
||||
num = "".join(c for c in part if c.isdigit())
|
||||
parts.append(int(num) if num else 0)
|
||||
|
||||
return tuple(parts) + (0, 0)
|
||||
return tuple(parts) + (0, 0, 0)
|
||||
|
||||
|
||||
def is_newer_version(latest: str, current: str) -> bool:
|
||||
@@ -121,9 +123,9 @@ def is_newer_version(latest: str, current: str) -> bool:
|
||||
latest_parsed = parse_version(latest)
|
||||
current_parsed = parse_version(current)
|
||||
|
||||
# Compare (major, minor, patch) first
|
||||
latest_base = latest_parsed[:3]
|
||||
current_base = current_parsed[:3]
|
||||
# Compare (major, minor, patch, micro) first
|
||||
latest_base = latest_parsed[:4]
|
||||
current_base = current_parsed[:4]
|
||||
|
||||
if latest_base > current_base:
|
||||
return True
|
||||
@@ -133,8 +135,8 @@ def is_newer_version(latest: str, current: str) -> bool:
|
||||
# Same base version - compare prerelease status
|
||||
# is_prerelease: 0 = release, 1 = prerelease
|
||||
# Release (0) should be "greater" than prerelease (1)
|
||||
latest_is_prerelease = latest_parsed[3] if len(latest_parsed) > 3 else 0
|
||||
current_is_prerelease = current_parsed[3] if len(current_parsed) > 3 else 0
|
||||
latest_is_prerelease = latest_parsed[4] if len(latest_parsed) > 4 else 0
|
||||
current_is_prerelease = current_parsed[4] if len(current_parsed) > 4 else 0
|
||||
|
||||
if latest_is_prerelease < current_is_prerelease:
|
||||
# latest is release, current is prerelease -> latest is newer
|
||||
@@ -145,8 +147,8 @@ def is_newer_version(latest: str, current: str) -> bool:
|
||||
|
||||
# Both are same type (both release or both prerelease)
|
||||
# Compare prerelease numbers
|
||||
latest_prerelease_num = latest_parsed[4] if len(latest_parsed) > 4 else 0
|
||||
current_prerelease_num = current_parsed[4] if len(current_parsed) > 4 else 0
|
||||
latest_prerelease_num = latest_parsed[5] if len(latest_parsed) > 5 else 0
|
||||
current_prerelease_num = current_parsed[5] if len(current_parsed) > 5 else 0
|
||||
|
||||
return latest_prerelease_num > current_prerelease_num
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
# Application version - single source of truth
|
||||
APP_VERSION = "0.1.9b"
|
||||
APP_VERSION = "0.1.8.1"
|
||||
GITHUB_REPO = "maziggy/bambuddy"
|
||||
|
||||
# App directory - where the application is installed (for static files)
|
||||
|
||||
Reference in New Issue
Block a user