spacr.qt.screens.data_manager

The Data Manager screen: disk usage, pruning and archiving for one project.

Three surfaces over spacr.data_manager, in the order a user needs them:

Usage — one row per artifact kind, measured by walking the project, with the unregistered bytes called out. The “unregistered” row counts bytes spaCR did not write and has no record of; none of them is ever offered for deletion.

Prune — the plan, in full, before anything happens. Every candidate with its size and the module that would make it again; every kept item with the rule that kept it, so the user can see why less space is freed than expected. Deleting requires the confirmation dialog, and the dialog shows the file list and the total one more time.

Archive — move the project, or part of it, somewhere else, and leave the record that says where it went.

The screen never deletes anything itself: it holds a spacr.data_manager.PrunePlan and hands it back with its own token. A plan that has gone stale — because a run wrote into the project while the screen was open — is refused by the module, not by this file, which is where that check belongs.

Registered through spacr.qt.app.register_app() and spacr.qt.theme.register_widget_qss() rather than by editing app.py or theme.py.

Classes

ConfirmDeleteDialog

Confirmation dialog shown before a prune plan deletes any files.

DataManagerScreen

Disk usage, pruning and archiving for one project.

Functions

make_data_manager_screen(→ PySide6.QtWidgets.QWidget)

Factory handed to spacr.qt.app.register_app().

register(→ bool)

Put the Data Manager in the app registry. Idempotent.

Module Contents

class spacr.qt.screens.data_manager.ConfirmDeleteDialog(plan: spacr.data_manager.PrunePlan, parent=None, *, threaded: bool = True)[source]

Bases: PySide6.QtWidgets.QDialog

Confirmation dialog shown before a prune plan deletes any files.

It shows the total, the item list and every file that would go — the same list spacr.data_manager.PrunePlan.file_list() returns, not a summary of it — and its accept button stays disabled until the user ticks the box that says they have read it. The box is not pre-ticked and is not a plain OK, because the deletion cannot be undone.

The dialog fills in two stages. The file list is not stored on the plan: every call to file_list runs os.walk over every candidate directory, because a plan for a project with millions of crops must not carry millions of strings. Doing that in __init__ would put a full recursive walk of the user’s project on the GUI thread, at the one moment they are waiting for a window to appear — and on a share that is asleep, the walk’s first stat alone can take twenty seconds while the mount wakes. So the dialog opens with the half that needs nothing from the disk (the totals, every candidate, every kept item and its reason) and the file list arrives behind it. Nothing is dropped: the user still reads every path before anything is deleted, which is why the acknowledgement is held disabled until the list is on screen — or, when the walk fails, until _on_listing_failed() has said so in the list’s place. The box is never enabled while the list shows a placeholder.

Parameters:
  • plan – the plan to confirm.

  • parent – Qt parent.

  • threaded – enumerate the files off the GUI thread. False walks inline, so a test has the finished dialog when the constructor returns; both paths produce the same text and the same enabled states.

Ask the user to confirm a deletion, in words they must read.

Parameters:
  • plan – what is about to be deleted.

  • parent – parent widget.

  • threaded – whether the deletion runs on a worker.

closeEvent(event)[source]

The same teardown for the window’s own close button.

Parameters:

event – the close event.

describe() → str[source]

The whole text — the plan, then every file — walked right now.

This walks the project, and the dialog does not call it. spacr.data_manager.PrunePlan.file_list() runs os.walk over every candidate each time it is asked, which can block for many seconds, so nothing on the GUI thread may call this. The dialog builds the same string in two pieces instead: _heading() at once, then _with_the_files() on the walk that _start_the_file_list() sent to a worker.

It stays public, and blocking, because the whole text in one call is what a caller outside Qt wants — a test, or a CLI that has a thread to spare. Read listing for what is actually on screen.

done(result: int) → None[source]

Close the dialog, having first stopped the walk.

Both Delete and Cancel come through here and neither sends a close event — QDialog.done hides the widget — so this, not closeEvent, is where a modal dialog’s teardown has to live. Qt aborts the process outright if a running QThread is destroyed with its owner, and the walk can still be out when the user cancels.

Parameters:

result – the dialog code to finish with.

class spacr.qt.screens.data_manager.DataManagerScreen(parent=None, *, project: str = '', threaded: bool = True)[source]

Bases: PySide6.QtWidgets.QWidget

Disk usage, pruning and archiving for one project.

Parameters:
  • parent – Qt parent.

  • project – open straight onto this project root, skipping the folder picker.

  • threaded – run the scan and the plan on a worker thread. Tests pass False so a scan is finished when the call returns; both paths run the same code and emit the same signals.

Build the manager’s three tabs and its totals row.

Parameters:

parent – parent widget.

choose_destination() → None[source]

Ask where an archive should go.

choose_project() → None[source]

Ask for a project folder and scan it.

closeEvent(event)[source]

Stop background work and unlink before going away.

Parameters:

event – the Qt close event.

confirm_and_archive() → bool[source]

Ask once, then move.

confirm_and_prune() → bool[source]

Show the confirmation dialog and, if accepted, delete.

execute_prune(plan: spacr.data_manager.PrunePlan) → bool[source]

Carry out plan. The screen’s only destructive call.

The token comes from the plan the screen is holding, so a plan that has gone stale — a run wrote into the project while this was open — is refused by spacr.data_manager.prune(), which re-checks the tree, rather than by a check duplicated here that could answer differently.

Parameters:

plan – the prune plan to carry out; its own token is passed as the confirmation to spacr.data_manager.prune().

plan_archive() → bool[source]

Work out what an archive would move. Moves nothing.

plan_prune() → bool[source]

Work out what could be deleted. Deletes nothing.

scan() → bool[source]

Measure the project. Off the GUI thread unless threaded=False.

selected_kinds() → List[str][source]

The kinds the prune tab is currently asking about.

set_destination(path: str) → None[source]

Set the archive destination without touching anything.

Parameters:

path – the archive destination folder; stored as a string and shown, and any previous archive plan is discarded.

set_project(root: str) → None[source]

Point the screen at root and scan it.

Parameters:

root – the project folder to show and scan; previous scan results and plans are discarded.

property plan: spacr.data_manager.PrunePlan | None[source]

The last prune plan, or None.

property project: str[source]

The project root this screen is showing.

property usage: spacr.data_manager.ProjectUsage | None[source]

The last scan, or None.

spacr.qt.screens.data_manager.make_data_manager_screen(app_key: str | None = None) → PySide6.QtWidgets.QWidget[source]

Factory handed to spacr.qt.app.register_app().

spacr.qt.screens.data_manager.register() → bool[source]

Put the Data Manager in the app registry. Idempotent.

Called at import time, so importing this module is all it takes for the app to exist. Returns rather than raises on a duplicate key, so a re-import is a no-op instead of failing the import.

Returns:

True when this call is what registered it.

Nested helpers

DataManagerScreen._follow_path_probes.redraw(path: str, _answer: bool) → None

Re-run the enable pass now that this root’s state is known.

Parameters:
  • path – the path whose probe just answered.

  • _answer – what it answered; unused, because _update_controls reads it back from the cache along with everything else it depends on.

spacr/qt/screens/data_manager.py:970

DataManagerScreen._run._job(payload: Dict[str, Any]) → None

Call the wrapped function, stashing its result in the payload.

The payload is how a value crosses back from the worker: a return would be swallowed by the runner.

spacr/qt/screens/data_manager.py:840

DataManagerScreen.plan_prune.plan()

The same worker-side isdir as scan(), for the same reason.

spacr/qt/screens/data_manager.py:1075

DataManagerScreen.scan.measure()

Measure root, having first made sure it is a folder.

The isdir check runs here, on the worker thread, rather than in the guard on the GUI thread. It raises the same message the guard uses, so a root that is not a folder is reported the same way in both places.

spacr/qt/screens/data_manager.py:1014