v1.7.0: dual-pane SFTP file manager
Replace primitive upload/download form with a full dual-pane file manager (local + remote) featuring directory browsing, navigation history, file operations (upload, download, mkdir, delete, rename), progress tracking, and persistent SFTP sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -308,5 +308,78 @@ class SSHClientWrapper:
|
||||
return f"Key generated: {self.key_path}"
|
||||
|
||||
|
||||
class SFTPSession:
|
||||
"""Persistent SFTP session for file browser."""
|
||||
|
||||
def __init__(self, server: dict, key_path: str):
|
||||
self.server = server
|
||||
self.key_path = key_path
|
||||
self._client: paramiko.SSHClient | None = None
|
||||
self._sftp: paramiko.SFTPClient | None = None
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
try:
|
||||
return (
|
||||
self._client is not None
|
||||
and self._sftp is not None
|
||||
and self._client.get_transport() is not None
|
||||
and self._client.get_transport().is_active()
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def connect(self):
|
||||
self._client = _connect_client(self.server, self.key_path)
|
||||
self._sftp = self._client.open_sftp()
|
||||
|
||||
def disconnect(self):
|
||||
if self._sftp:
|
||||
try:
|
||||
self._sftp.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._sftp = None
|
||||
if self._client:
|
||||
try:
|
||||
self._client.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._client = None
|
||||
|
||||
def listdir_attr(self, path: str) -> list:
|
||||
return self._sftp.listdir_attr(path)
|
||||
|
||||
def stat(self, path: str):
|
||||
return self._sftp.stat(path)
|
||||
|
||||
def mkdir(self, path: str):
|
||||
self._sftp.mkdir(path)
|
||||
|
||||
def rmdir(self, path: str):
|
||||
self._sftp.rmdir(path)
|
||||
|
||||
def remove(self, path: str):
|
||||
self._sftp.remove(path)
|
||||
|
||||
def rename(self, old: str, new: str):
|
||||
self._sftp.rename(old, new)
|
||||
|
||||
def upload(self, local_path: str, remote_path: str, progress_cb=None):
|
||||
if progress_cb:
|
||||
self._sftp.put(local_path, remote_path, callback=progress_cb)
|
||||
else:
|
||||
self._sftp.put(local_path, remote_path)
|
||||
|
||||
def download(self, remote_path: str, local_path: str, progress_cb=None):
|
||||
if progress_cb:
|
||||
self._sftp.get(remote_path, local_path, callback=progress_cb)
|
||||
else:
|
||||
self._sftp.get(remote_path, local_path)
|
||||
|
||||
def normalize(self, path: str) -> str:
|
||||
return self._sftp.normalize(path)
|
||||
|
||||
|
||||
def _shell_quote(s: str) -> str:
|
||||
return "'" + s.replace("'", "'\\''") + "'"
|
||||
|
||||
Reference in New Issue
Block a user