Up-/Download gefixt: absoluter Remote-Pfad statt "/./datei" (#3)
paramikos getcwd() liefert None, solange kein chdir() erfolgt ist. Der
Rueckfall auf "." landete als remote_path in der App, woraus _join()/_abs()
Pfade wie "/./datei" bauten. Der Server loeste die relativ zum Dateisystem-
Root statt zum Home auf -> "No such file" beim Download, "Permission denied"
beim Upload. Das Listing funktionierte, weil listdir(".") serverseitig
relativ zum Home aufgeloest wird -- daher fiel es erst beim Transfer auf.
- connect()/chdir()/listdir() loesen den Pfad ueber sftp.normalize() auf
- _abs() haengt relative Pfade an das CWD an, statt blind "/" voranzustellen
- _copy_tree(): die beiden mkdir-Zweige waren vertauscht. Beim Download
wurde backend.mkdir() auf einen lokalen Pfad angewendet (Verzeichnis
entstand auf dem Server), beim Upload os.makedirs() auf einen entfernten.
Ordner-Transfers waren dadurch in beide Richtungen defekt.
Verifiziert per End-to-End-Test gegen einen echten sshd/SFTP-Server:
Datei- und rekursiver Ordner-Transfer in beide Richtungen.
Closes #3
This commit is contained in:
+4
-2
@@ -450,7 +450,8 @@ class BrowserScreen(Screen):
|
||||
def _copy_tree(self, src: str, dst: str, download: bool) -> None:
|
||||
try:
|
||||
if download:
|
||||
self.backend.mkdir(dst)
|
||||
# dst ist ein *lokaler* Pfad -> lokal anlegen, nicht auf dem Server
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
entries = self.backend.listdir(src)
|
||||
for e in entries:
|
||||
if e.kind == "dir":
|
||||
@@ -460,7 +461,8 @@ class BrowserScreen(Screen):
|
||||
else:
|
||||
self.backend.download(_join(src, e.name, False), _join(dst, e.name, True))
|
||||
else:
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
# dst ist ein *entfernter* Pfad -> auf dem Server anlegen
|
||||
self.backend.mkdir(dst)
|
||||
for item in os.scandir(src):
|
||||
if item.is_dir():
|
||||
self._copy_tree(
|
||||
|
||||
+22
-7
@@ -29,7 +29,21 @@ class SFTPBackend:
|
||||
timeout=30,
|
||||
)
|
||||
self._sftp = self._ssh.open_sftp()
|
||||
self._cwd = self._sftp.getcwd() or "."
|
||||
self._cwd = self._resolve(".")
|
||||
|
||||
def _resolve(self, path: str) -> str:
|
||||
"""Löst einen Pfad serverseitig zu einem absoluten Pfad auf.
|
||||
|
||||
``paramiko.SFTPClient.getcwd()`` liefert ``None``, solange kein
|
||||
explizites ``chdir()`` erfolgt ist – ein Rückfall auf ``"."`` würde
|
||||
später zu Pfaden wie ``/./datei`` führen, die der Server relativ zum
|
||||
Dateisystem-Root statt zum Home-Verzeichnis auflöst.
|
||||
"""
|
||||
assert self._sftp is not None
|
||||
try:
|
||||
return self._sftp.normalize(path)
|
||||
except OSError:
|
||||
return path if path.startswith("/") else "/"
|
||||
|
||||
def listdir(self, path: str) -> list[RemoteEntry]:
|
||||
assert self._sftp is not None
|
||||
@@ -45,22 +59,23 @@ class SFTPBackend:
|
||||
modified=attr.st_mtime,
|
||||
)
|
||||
)
|
||||
self._cwd = path
|
||||
self._cwd = self._resolve(path)
|
||||
return entries
|
||||
|
||||
def chdir(self, path: str) -> None:
|
||||
assert self._sftp is not None
|
||||
self._sftp.chdir(path)
|
||||
self._cwd = self._sftp.getcwd() or path
|
||||
self._cwd = self._resolve(".")
|
||||
|
||||
def pwd(self) -> str:
|
||||
return self._cwd
|
||||
|
||||
def _abs(self, path: str) -> str:
|
||||
"""Baut einen absolut wertigen Pfad relativ zum SFTP-Root."""
|
||||
if path.startswith("/"):
|
||||
return path or "/"
|
||||
return "/" + path
|
||||
"""Baut einen absoluten Pfad – relative Pfade gelten zum aktuellen CWD."""
|
||||
if not path:
|
||||
return self._cwd or "/"
|
||||
base = self._cwd if self._cwd.startswith("/") else "/"
|
||||
return str(PurePosixPath(base) / path)
|
||||
|
||||
def download(self, remote: str, local: str) -> None:
|
||||
assert self._sftp is not None
|
||||
|
||||
Reference in New Issue
Block a user