Add FTP/SFTP support and tree-view folder picker with lazy loading

FTP/SFTP processor:
- New ftp_processor.py with adapter pattern for FTP (passive) and SFTP
- Same design as smb_processor: read PDFs, forward via SMTP, move to processed
- Eingangs-/Ausgangsbelege with separate paths, modes (forward/separator)
- paramiko==3.5.0 for SFTP support
- Schema v9 with new ftp_* settings
- Integrated in scheduler

Tree-view folder picker (SMB + FTP):
- Reusable tree rendering from flat path lists
- Expandable/collapsible nodes with toggle arrows
- Lazy loading: only top-level folders on open, sub-folders on-demand
- Auto-expand ancestors of currently selected value (with preload)
- Reload button stays for manual refresh
- Always fresh load when opening picker
- New endpoints: /api/list-smb-subfolders, /api/list-ftp-subfolders

FTP-specific fixes:
- list_pdfs uses LIST instead of NLST (more reliable across servers)
- Stateful CWD bug fixed in ensure_dir/stat_exists/rename
  (previously created /Buch/Buch/X instead of /Buch/X due to CWD drift)
- All operations reset CWD via _reset_cwd() before stateful calls
- _resolve() helper for SFTP to handle empty path / chroot users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 13:26:42 +02:00
parent 35366e0c1b
commit 4b9df132d7
9 changed files with 1287 additions and 28 deletions
+20 -2
View File
@@ -4,13 +4,13 @@ import aiosqlite
from cryptography.fernet import Fernet
DB_PATH = os.environ.get("DB_PATH", "/data/belegimport.db")
SCHEMA_VERSION = 8
SCHEMA_VERSION = 9
logger = logging.getLogger(__name__)
_fernet = None
ENCRYPTED_KEYS = {"imap_password", "smtp_password", "smb_password", "amazon_password", "amazon_client_secret", "amazon_refresh_token"}
ENCRYPTED_KEYS = {"imap_password", "smtp_password", "smb_password", "amazon_password", "amazon_client_secret", "amazon_refresh_token", "ftp_password"}
DEFAULT_SETTINGS = {
"imap_server": "",
@@ -46,6 +46,18 @@ DEFAULT_SETTINGS = {
"smb_source_path_ausgang": "",
"smb_processed_path_ausgang": "",
"smb_mode": "forward",
# FTP / SFTP
"ftp_enabled": "false",
"ftp_protocol": "sftp", # "sftp" or "ftp"
"ftp_server": "",
"ftp_port": "22",
"ftp_username": "",
"ftp_password": "",
"ftp_source_path": "",
"ftp_processed_path": "Verarbeitet",
"ftp_source_path_ausgang": "",
"ftp_processed_path_ausgang": "",
"ftp_mode": "forward",
# Amazon
"amazon_enabled": "false",
"amazon_email": "",
@@ -241,6 +253,12 @@ async def _run_migrations(db: aiosqlite.Connection, current_version: int):
await db.commit()
await _set_schema_version(db, 8)
if current_version < 9:
logger.info("Migration v9: FTP/SFTP-Settings hinzugefuegt (defaults werden eingefuegt)")
# No table changes needed - new settings are added via DEFAULT_SETTINGS loop in init_db
await db.commit()
await _set_schema_version(db, 9)
await db.commit()