aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-cli/src/meshbay_cli/tui/screens.py')
-rw-r--r--packages/meshbay-cli/src/meshbay_cli/tui/screens.py59
1 files changed, 59 insertions, 0 deletions
diff --git a/packages/meshbay-cli/src/meshbay_cli/tui/screens.py b/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
index e1ab620..0306325 100644
--- a/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
+++ b/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
@@ -6,6 +6,10 @@ with the rules `get` and `put` follow: an upload is decided whole before it
starts (names the node refuses stop it, names already there are asked about
once for the batch), nothing on either side is replaced without being asked,
and a name from a group is made writable here before it is written.
+
+Delete removes the selection from a group with the rules `rm` follows: files
+only, every one of them this account's to delete (uploaded by it, or this key
+the node's operator) or none is, then asked once for the batch.
"""
import asyncio
@@ -24,6 +28,7 @@ from ..client.account import Account
from ..client.names import UnsafeName, local_path
from ..client.paths import handle
from ..client.pool import Pool
+from ..client.remove import delete_file, may_delete
from ..client.store import FILE_KEY_NOTICE, KEY_FILE
from ..client.transfers import PART_SUFFIX, TransferError, create_dir, download, upload
from ..client.tree import Tree
@@ -95,6 +100,7 @@ class SignInScreen(Screen):
class MainScreen(Screen):
BINDINGS = [
Binding("f5", "copy", "Copy"),
+ Binding("delete", "delete", "Delete"),
Binding("q", "app.quit", "Quit"),
Binding("tab", "app.focus_next", "Switch", show=False),
]
@@ -331,6 +337,59 @@ class MainScreen(Screen):
except TransferError as e:
self.strip.update_job(job, state="failed", note=e.message)
+ # ── Delete ───────────────────────────────────────────────────────────────
+
+ def action_delete(self) -> None:
+ focused = self.app.focused
+ if focused is None or self.remote not in focused.ancestors_with_self:
+ self.app.notify("Delete removes files from a group: select them on the "
+ "right.", timeout=4)
+ return
+ self.run_worker(self._delete(), group="delete")
+
+ async def _delete(self) -> None:
+ remote = self.remote
+ rows = remote.selection()
+ if remote.group is None or not rows:
+ self.app.notify("Open a group and choose the files to delete.", timeout=4)
+ return
+ group = remote.group
+ try:
+ session = await self.pool.session(group)
+ except CliError as e:
+ self.app.notify(e.message, severity="error", markup=False)
+ return
+ folders = [r.name for r in rows if r.is_dir]
+ not_yours = [r.name for r in rows if not r.is_dir
+ and not may_delete(session, r.data["entry"], self.account.user_id)]
+ if folders or not_yours:
+ why = (["Folders are not deleted here: " + ", ".join(map(safe, folders))]
+ if folders else [])
+ if not_yours:
+ why.append("You did not upload these, and this machine's key is not the "
+ "node's operator: " + ", ".join(map(safe, not_yours)))
+ await self._ask("Nothing was deleted.\n" + "\n".join(why), [("ok", "OK")])
+ return
+ names = ", ".join(safe(r.name) for r in rows[:5]) + (
+ f" and {len(rows) - 5} more" if len(rows) > 5 else "")
+ answer = await self._ask(
+ f"Delete {len(rows)} file(s) from {safe(handle(group))} for every member? "
+ f"{names}", [("delete", "Delete"), ("cancel", "Cancel")])
+ if answer != "delete":
+ return
+ remote.marks.clear()
+ deleted = 0
+ for row in rows:
+ try:
+ await delete_file(session, row.data["entry"]["id"])
+ deleted += 1
+ except CliError as e:
+ self.app.notify(f"{safe(row.name)}: {e.message}", severity="error",
+ markup=False, timeout=8)
+ if deleted:
+ self.app.notify(f"Deleted {deleted} file(s).", timeout=4)
+ self._changed(group)
+
async def on_unmount(self) -> None:
with contextlib.suppress(Exception):
await self.pool.close()