Skip to content
Open
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
22 changes: 19 additions & 3 deletions src/portkeydrop/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,21 @@ def _on_delete(self, event) -> None:
else:
self._delete_remote()

def _resolve_remote_file_path(self, remote_file: RemoteFile) -> str:
path = (remote_file.path or remote_file.name).strip()
if not path.startswith("/"):
base = self._client.cwd if self._client else "/"
path = f"{base.rstrip('/')}/{path}" if base != "/" else f"/{path.lstrip('/')}"
if remote_file.is_dir and remote_file.path.endswith("/") and not path.endswith("/"):
path += "/"
return path or "/"

def _resolve_local_file_path(self, remote_file: RemoteFile) -> str:
path = Path(remote_file.path or remote_file.name).expanduser()
if not path.is_absolute():
path = Path(self._local_cwd) / path
return str(path.resolve(strict=False))

def _delete_remote(self) -> None:
f = self._get_selected_remote_file()
if not f or not self._client or f.name == "..":
Expand All @@ -1942,11 +1957,12 @@ def _delete_remote(self) -> None:
)
if result == wx.YES:
try:
target_path = self._resolve_remote_file_path(f)
self._update_status(f"Deleting {f.name}...", self._client.cwd)
if f.is_dir:
self._client.rmdir(f.path)
self._client.rmdir(target_path)
else:
self._client.delete(f.path)
self._client.delete(target_path)
self._announce(f"Deleted {f.name}")
self._update_status("Delete complete.", self._client.cwd)
self._play_sound_event("delete_complete")
Expand All @@ -1965,7 +1981,7 @@ def _delete_local(self) -> None:
)
if result == wx.YES:
try:
delete_local(f.path)
delete_local(self._resolve_local_file_path(f))
self._announce(f"Deleted {f.name}")
self._play_sound_event("delete_complete")
self._refresh_local_files()
Expand Down
6 changes: 4 additions & 2 deletions src/portkeydrop/local_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ def parent_local(current: str | Path) -> Path:

def delete_local(path: str | Path) -> None:
"""Delete a local file or directory."""
p = Path(path)
if p.is_dir():
p = Path(path).expanduser()
if p.is_symlink() or p.is_file():
p.unlink()
elif p.is_dir():
shutil.rmtree(p)
else:
p.unlink()
Expand Down
32 changes: 32 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,38 @@ def test_delete_remote_updates_status_on_success(app_module):
frame._play_sound_event.assert_called_with("delete_complete")


def test_delete_remote_resolves_relative_path_against_cwd(app_module):
app, fake_wx = app_module
frame = _hydrate_frame(app_module)
frame._client = MagicMock(connected=True, cwd="/remote")
remote = MagicMock(name="doc.txt")
remote.name = "doc.txt"
remote.path = "doc.txt"
remote.is_dir = False
frame._get_selected_remote_file.return_value = remote
fake_wx.MessageBox.return_value = fake_wx.YES

frame._delete_remote()

frame._client.delete.assert_called_once_with("/remote/doc.txt")


def test_delete_local_resolves_relative_path_against_cwd(app_module):
app, fake_wx = app_module
frame = _hydrate_frame(app_module)
frame._local_cwd = "/tmp/example"
local = MagicMock(name="doc.txt")
local.name = "doc.txt"
local.path = "doc.txt"
frame._get_selected_local_file.return_value = local
fake_wx.MessageBox.return_value = fake_wx.YES

with patch.object(app, "delete_local") as delete_local_mock:
frame._delete_local()

delete_local_mock.assert_called_once_with("/tmp/example/doc.txt")


def test_delete_remote_reports_failure(app_module):
app, fake_wx = app_module
frame = _hydrate_frame(app_module)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_local_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ def test_delete_dir(self, tmp_path):
delete_local(d)
assert not d.exists()

def test_delete_directory_symlink_unlinks_link_only(self, tmp_path):
target = tmp_path / "target"
target.mkdir()
(target / "inner.txt").write_text("x")
link = tmp_path / "target-link"
link.symlink_to(target, target_is_directory=True)

delete_local(link)

assert not link.exists()
assert target.exists()
assert (target / "inner.txt").exists()

def test_delete_nonexistent_raises(self, tmp_path):
with pytest.raises(FileNotFoundError):
delete_local(tmp_path / "nope.txt")
Expand Down
Loading