spacr.qt.screens.data_manager

The Data Manager screen — what the project costs, and how to get it back.

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 row that is always the surprise is “unregistered”: bytes spaCR did not put there and cannot account for, which is also why 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, because a user who expected 300 GB back and was offered 12 needs to read why rather than guess. 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

The last thing between a plan and an irreversible deletion.

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

The last thing between a plan and an irreversible deletion.

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 deliberately not pre-ticked and deliberately not a plain OK: this data is somebody’s experiment and there is no undo.

IT FILLS IN TWO STAGES, and the reason is the whole point of this exercise. 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__ 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 it is the walk’s FIRST stat that spends twenty seconds waking the mount, not the walk. 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 one thing that never happens is that box being armed over 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.

WALKS THE PROJECT, AND THE DIALOG NO LONGER CALLS IT. spacr.data_manager.PrunePlan.file_list() runs os.walk over every candidate each time it is asked, which is the twenty seconds this exercise removed, 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 taking the import down.

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:955

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:825

DataManagerScreen.plan_prune.plan()

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

spacr/qt/screens/data_manager.py:1058

DataManagerScreen.scan.measure()

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

The isdir the guard above can no longer do lives here, where the thread waiting on it is a worker and waiting is free. The sentence it raises is the one the guard has always said, because a root that is not a folder has to read the same to the user however spaCR found out.

spacr/qt/screens/data_manager.py:996