clutterxp 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clutterxp/__init__.py +4 -0
- clutterxp/cli/__init__.py +7 -0
- clutterxp/cli/app.py +251 -0
- clutterxp/cli/cmd_config.py +250 -0
- clutterxp/cli/cmd_doctor.py +449 -0
- clutterxp/cli/cmd_duplicates.py +253 -0
- clutterxp/cli/cmd_gui.py +22 -0
- clutterxp/cli/cmd_history.py +233 -0
- clutterxp/cli/cmd_init.py +76 -0
- clutterxp/cli/cmd_organize.py +226 -0
- clutterxp/cli/cmd_rules.py +540 -0
- clutterxp/cli/cmd_scan.py +109 -0
- clutterxp/cli/cmd_service.py +130 -0
- clutterxp/cli/cmd_undo.py +180 -0
- clutterxp/cli/cmd_watch.py +147 -0
- clutterxp/cli/errors.py +166 -0
- clutterxp/cli/render.py +390 -0
- clutterxp/config/__init__.py +40 -0
- clutterxp/config/jsonschema.py +12 -0
- clutterxp/config/loader.py +212 -0
- clutterxp/config/placeholders.py +79 -0
- clutterxp/config/runtime.py +30 -0
- clutterxp/config/schema.py +166 -0
- clutterxp/config/validate.py +226 -0
- clutterxp/config/writer.py +248 -0
- clutterxp/config.example.yaml +76 -0
- clutterxp/duplicates/__init__.py +22 -0
- clutterxp/duplicates/detector.py +438 -0
- clutterxp/duplicates/hashcache.py +149 -0
- clutterxp/duplicates/namenorm.py +45 -0
- clutterxp/duplicates/quarantine.py +196 -0
- clutterxp/duplicates/report.py +110 -0
- clutterxp/gui/__init__.py +1 -0
- clutterxp/gui/api.py +494 -0
- clutterxp/gui/app.py +205 -0
- clutterxp/gui/models_qt.py +544 -0
- clutterxp/gui/views/__init__.py +21 -0
- clutterxp/gui/views/dashboard.py +253 -0
- clutterxp/gui/views/duplicates.py +404 -0
- clutterxp/gui/views/folders.py +354 -0
- clutterxp/gui/views/history.py +281 -0
- clutterxp/gui/views/preview.py +277 -0
- clutterxp/gui/views/rules.py +527 -0
- clutterxp/gui/views/settings.py +172 -0
- clutterxp/gui/workers.py +147 -0
- clutterxp/history/__init__.py +23 -0
- clutterxp/history/db.py +174 -0
- clutterxp/history/pathcodec.py +42 -0
- clutterxp/history/recovery.py +318 -0
- clutterxp/history/schema.sql +43 -0
- clutterxp/history/service.py +493 -0
- clutterxp/history/undo.py +373 -0
- clutterxp/interfaces.py +164 -0
- clutterxp/locking.py +113 -0
- clutterxp/logging_setup.py +73 -0
- clutterxp/models.py +266 -0
- clutterxp/mover/__init__.py +26 -0
- clutterxp/mover/collision.py +46 -0
- clutterxp/mover/fsops.py +191 -0
- clutterxp/mover/safe_move.py +741 -0
- clutterxp/organizer.py +388 -0
- clutterxp/packaging/clutterxp.service +15 -0
- clutterxp/paths.py +112 -0
- clutterxp/planner.py +339 -0
- clutterxp/py.typed +0 -0
- clutterxp/rules/__init__.py +16 -0
- clutterxp/rules/engine.py +374 -0
- clutterxp/rules/matcher.py +222 -0
- clutterxp/rules/template.py +171 -0
- clutterxp/rules/tokenizer.py +118 -0
- clutterxp/safety.py +111 -0
- clutterxp/scanner.py +196 -0
- clutterxp/service_install.py +208 -0
- clutterxp/units.py +59 -0
- clutterxp/watch/__init__.py +37 -0
- clutterxp/watch/debounce.py +84 -0
- clutterxp/watch/events.py +259 -0
- clutterxp/watch/ignore.py +77 -0
- clutterxp/watch/service.py +662 -0
- clutterxp/watch/settle.py +170 -0
- clutterxp-1.1.0.dist-info/METADATA +149 -0
- clutterxp-1.1.0.dist-info/RECORD +84 -0
- clutterxp-1.1.0.dist-info/WHEEL +4 -0
- clutterxp-1.1.0.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Settler: file completion and stability detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import stat
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SettleStatus(str, Enum):
|
|
18
|
+
"""Result of checking whether a file is settled and ready to move."""
|
|
19
|
+
|
|
20
|
+
READY = "ready"
|
|
21
|
+
NOT_YET = "not_yet"
|
|
22
|
+
GONE = "gone"
|
|
23
|
+
DROP = "drop"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Settler:
|
|
27
|
+
"""Verifies file stability (stat checks and /proc open-for-write checks)."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
clock: Callable[[], float] = time.monotonic,
|
|
32
|
+
stat_fn: Callable[[Path | str], Any] = os.lstat,
|
|
33
|
+
proc_dir: Path = Path("/proc"),
|
|
34
|
+
*,
|
|
35
|
+
sample_interval: float = 0.5,
|
|
36
|
+
max_wait: float = 6 * 3600.0,
|
|
37
|
+
) -> None:
|
|
38
|
+
self._clock = clock
|
|
39
|
+
self._stat_fn = stat_fn
|
|
40
|
+
self._proc_dir = proc_dir
|
|
41
|
+
self.sample_interval = sample_interval
|
|
42
|
+
self.max_wait = max_wait
|
|
43
|
+
self._samples: dict[Path, tuple[tuple[int, int], float]] = {}
|
|
44
|
+
self._first_seen: dict[Path, float] = {}
|
|
45
|
+
|
|
46
|
+
def clear(self, path: Path) -> None:
|
|
47
|
+
"""Clear recorded samples and first-seen timestamp for path."""
|
|
48
|
+
self._samples.pop(path, None)
|
|
49
|
+
self._first_seen.pop(path, None)
|
|
50
|
+
|
|
51
|
+
def check(self, path: Path, settle_seconds: float) -> SettleStatus:
|
|
52
|
+
"""Check whether candidate file is settled and safe to move.
|
|
53
|
+
|
|
54
|
+
Steps:
|
|
55
|
+
1. Regular file check: must exist, regular file, not symlink.
|
|
56
|
+
2. Stable stat check: 2 samples >= sample_interval apart with identical (size, mtime_ns).
|
|
57
|
+
3. Open for write check: no process on Linux holds path open for write.
|
|
58
|
+
4. If all pass -> READY.
|
|
59
|
+
"""
|
|
60
|
+
# Step 1: Check existence and regular file status
|
|
61
|
+
try:
|
|
62
|
+
st = self._stat_fn(path)
|
|
63
|
+
except (FileNotFoundError, OSError):
|
|
64
|
+
self.clear(path)
|
|
65
|
+
return SettleStatus.GONE
|
|
66
|
+
|
|
67
|
+
is_symlink = getattr(st, "is_symlink", False)
|
|
68
|
+
is_regular = getattr(st, "is_regular", True)
|
|
69
|
+
mode = getattr(st, "st_mode", None)
|
|
70
|
+
if mode is not None:
|
|
71
|
+
is_symlink = stat.S_ISLNK(mode)
|
|
72
|
+
is_regular = stat.S_ISREG(mode)
|
|
73
|
+
|
|
74
|
+
if is_symlink or not is_regular:
|
|
75
|
+
self.clear(path)
|
|
76
|
+
return SettleStatus.DROP
|
|
77
|
+
|
|
78
|
+
now = self._clock()
|
|
79
|
+
|
|
80
|
+
# Track continuous activity and enforce max_wait
|
|
81
|
+
if path not in self._first_seen:
|
|
82
|
+
self._first_seen[path] = now
|
|
83
|
+
elif (now - self._first_seen[path]) > self.max_wait:
|
|
84
|
+
logger.warning(
|
|
85
|
+
"Candidate %s has not settled after %s seconds; dropping",
|
|
86
|
+
path,
|
|
87
|
+
self.max_wait,
|
|
88
|
+
)
|
|
89
|
+
self.clear(path)
|
|
90
|
+
return SettleStatus.DROP
|
|
91
|
+
|
|
92
|
+
# Step 2: Two-sample stable stat check
|
|
93
|
+
size = getattr(st, "st_size", getattr(st, "size", 0))
|
|
94
|
+
mtime_ns = getattr(st, "st_mtime_ns", getattr(st, "mtime_ns", 0))
|
|
95
|
+
current_stat = (size, mtime_ns)
|
|
96
|
+
|
|
97
|
+
interval = min(self.sample_interval, settle_seconds) if settle_seconds > 0 else 0.0
|
|
98
|
+
|
|
99
|
+
if path not in self._samples:
|
|
100
|
+
self._samples[path] = (current_stat, now)
|
|
101
|
+
return SettleStatus.NOT_YET
|
|
102
|
+
|
|
103
|
+
prev_stat, prev_time = self._samples[path]
|
|
104
|
+
if current_stat != prev_stat:
|
|
105
|
+
self._samples[path] = (current_stat, now)
|
|
106
|
+
return SettleStatus.NOT_YET
|
|
107
|
+
|
|
108
|
+
if (now - prev_time) < interval:
|
|
109
|
+
return SettleStatus.NOT_YET
|
|
110
|
+
|
|
111
|
+
# Step 3: Not open for writing
|
|
112
|
+
if self.is_open_for_write(path):
|
|
113
|
+
return SettleStatus.NOT_YET
|
|
114
|
+
|
|
115
|
+
# Step 4: Ready
|
|
116
|
+
self.clear(path)
|
|
117
|
+
return SettleStatus.READY
|
|
118
|
+
|
|
119
|
+
def is_open_for_write(self, path: Path) -> bool:
|
|
120
|
+
"""Inspect /proc to determine if any process holds path open for write."""
|
|
121
|
+
if not self._proc_dir.exists() or not self._proc_dir.is_dir():
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
target_real = os.path.realpath(str(path))
|
|
126
|
+
except OSError:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
proc_entries = list(self._proc_dir.iterdir())
|
|
131
|
+
except OSError:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
for pid_entry in proc_entries:
|
|
135
|
+
if not pid_entry.name.isdigit():
|
|
136
|
+
continue
|
|
137
|
+
fd_dir = pid_entry / "fd"
|
|
138
|
+
try:
|
|
139
|
+
fd_entries = list(fd_dir.iterdir())
|
|
140
|
+
except OSError:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
for fd_path in fd_entries:
|
|
144
|
+
try:
|
|
145
|
+
link_dest = str(fd_path.readlink())
|
|
146
|
+
if link_dest == target_real or link_dest == f"{target_real} (deleted)":
|
|
147
|
+
fdinfo_path = pid_entry / "fdinfo" / fd_path.name
|
|
148
|
+
flags = self._read_fdinfo_flags(fdinfo_path)
|
|
149
|
+
if flags is not None and (flags & os.O_ACCMODE) != os.O_RDONLY:
|
|
150
|
+
return True
|
|
151
|
+
except OSError:
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
def _read_fdinfo_flags(self, fdinfo_path: Path) -> int | None:
|
|
157
|
+
"""Read and parse octal 'flags:' field from /proc/<pid>/fdinfo/<fd>."""
|
|
158
|
+
try:
|
|
159
|
+
with fdinfo_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
160
|
+
for line in f:
|
|
161
|
+
if line.startswith("flags:"):
|
|
162
|
+
parts = line.split()
|
|
163
|
+
if len(parts) >= 2:
|
|
164
|
+
return int(parts[1], 8)
|
|
165
|
+
except OSError:
|
|
166
|
+
pass
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
__all__ = ["SettleStatus", "Settler"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: clutterxp
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Configurable, safe, lightweight file organizer: rules, preview, undo, watch, duplicates.
|
|
5
|
+
Keywords: automation,cli,downloads,file-organizer,linux
|
|
6
|
+
Classifier: Environment :: Console
|
|
7
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Topic :: System :: Filesystems
|
|
10
|
+
Classifier: Topic :: Utilities
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: platformdirs>=4.0
|
|
13
|
+
Requires-Dist: pydantic>=2.6
|
|
14
|
+
Requires-Dist: ruamel-yaml-clib>=0.2.12; platform_python_implementation == 'CPython'
|
|
15
|
+
Requires-Dist: ruamel-yaml>=0.18
|
|
16
|
+
Requires-Dist: typer>=0.12
|
|
17
|
+
Requires-Dist: watchdog>=4.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
21
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
26
|
+
Provides-Extra: gui
|
|
27
|
+
Requires-Dist: pyside6>=6.6; extra == 'gui'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# ClutterXP
|
|
31
|
+
|
|
32
|
+
> **Tired of a messy Downloads folder?** ClutterXP quietly tidies your files into the right place—on your terms, without ever losing a single file.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Whether your `Downloads` folder is buried under months of receipts, PDFs, wallpapers, installers, and screenshots, or you just want your desktop kept neat, **ClutterXP** does the heavy lifting for you.
|
|
37
|
+
|
|
38
|
+
Use the **clean visual desktop app** or run quick commands from your terminal.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why you can actually trust it
|
|
43
|
+
|
|
44
|
+
Most file cleanup tools feel risky—what if they delete something important or move a file you didn't want touched? We built ClutterXP with ironclad safety guarantees:
|
|
45
|
+
|
|
46
|
+
- 🛡️ **Never deletes or overwrites**: Files only move if the destination is safe. If a file with the same name already exists, ClutterXP renames the new one (e.g. `report (1).pdf`) or skips it. Your existing files are never replaced or destroyed.
|
|
47
|
+
- 🎯 **Only moves what you tell it to**: If a file doesn't match a rule you created, it stays exactly where it is. No surprise moves, no mystery "unsorted" dump folders.
|
|
48
|
+
- ↩️ **One-click instant undo**: Every action is saved in a local journal. If you ever change your mind, `clutterxp undo` (or a single click in the app) puts every file right back where it was originally.
|
|
49
|
+
- 🔒 **100% private and offline**: Runs completely on your machine. No accounts, no cloud sync, no tracking, and zero internet requests.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Getting Started
|
|
54
|
+
|
|
55
|
+
### 1. Installation
|
|
56
|
+
|
|
57
|
+
Install ClutterXP with Python (3.10+):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Recommended: install with desktop app (GUI) support
|
|
61
|
+
pip install "clutterxp[gui]"
|
|
62
|
+
|
|
63
|
+
# Or on Kali Linux / Debian using pipx:
|
|
64
|
+
pipx install .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Choose how you want to use it
|
|
68
|
+
|
|
69
|
+
#### Option A: The Desktop App
|
|
70
|
+
Just run:
|
|
71
|
+
```bash
|
|
72
|
+
clutterxp gui
|
|
73
|
+
```
|
|
74
|
+
You'll get an intuitive dashboard where you can:
|
|
75
|
+
- **Preview moves before they happen** with a clear interactive table.
|
|
76
|
+
- **Set up simple rules** with live matching tests (type a filename and see which rule catches it instantly).
|
|
77
|
+
- **Find duplicate files** and safely quarantine them without risking accidental deletion.
|
|
78
|
+
- **Undo any batch** with a single button.
|
|
79
|
+
|
|
80
|
+
#### Option B: The Terminal (Fast & Scriptable)
|
|
81
|
+
Prefer working from the command line? Use `clutterxp` or the short alias `cxp`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 1. Create your default starter config
|
|
85
|
+
clutterxp init
|
|
86
|
+
|
|
87
|
+
# 2. Test which rule matches a filename
|
|
88
|
+
clutterxp rules test "invoice_march.pdf"
|
|
89
|
+
|
|
90
|
+
# 3. Preview what would be organized (dry-run, touches nothing)
|
|
91
|
+
clutterxp organize ~/Downloads --dry-run
|
|
92
|
+
|
|
93
|
+
# 4. Organize your Downloads folder
|
|
94
|
+
clutterxp organize ~/Downloads --yes
|
|
95
|
+
|
|
96
|
+
# 5. Changed your mind? Undo the entire batch instantly!
|
|
97
|
+
clutterxp undo --yes
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Features that make life easier
|
|
103
|
+
|
|
104
|
+
### 📁 Set it and forget it (Background Watcher)
|
|
105
|
+
Turn on automatic sorting. Whenever a new download finishes, ClutterXP waits until it's completely written to disk, then quietly sorts it into place.
|
|
106
|
+
```bash
|
|
107
|
+
clutterxp watch
|
|
108
|
+
```
|
|
109
|
+
You can even enable it as a system background service:
|
|
110
|
+
```bash
|
|
111
|
+
clutterxp service install
|
|
112
|
+
clutterxp service status
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 🔍 Find and clean duplicate downloads
|
|
116
|
+
Downloaded the same PDF or installer three times? ClutterXP safely identifies duplicate files taking up storage, shows you where they live, and lets you isolate them safely.
|
|
117
|
+
```bash
|
|
118
|
+
clutterxp duplicates scan ~/Downloads
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### ✏️ Customize your rules in seconds
|
|
122
|
+
Rules are dead simple. Group by keywords, file extensions, size, or age:
|
|
123
|
+
- *"Move anything with `invoice` or `receipt` to `~/Documents/Invoices`"*
|
|
124
|
+
- *"Move `.png` and `.jpg` with `wallpaper` to `~/Pictures/Wallpapers`"*
|
|
125
|
+
- *"Move `.deb`, `.iso`, `.rpm` to `~/Downloads/Installers`"*
|
|
126
|
+
|
|
127
|
+
You can edit them directly in the visual app or via `clutterxp rules add`.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Cheat Sheet
|
|
132
|
+
|
|
133
|
+
| What you want to do | Command |
|
|
134
|
+
|:---|:---|
|
|
135
|
+
| Open the visual desktop app | `clutterxp gui` |
|
|
136
|
+
| Preview organization without moving files | `cxp organize ~/Downloads --dry-run` |
|
|
137
|
+
| Organize a folder | `cxp organize ~/Downloads` |
|
|
138
|
+
| Undo the last batch of moves | `cxp undo` |
|
|
139
|
+
| Watch a folder and sort new files automatically | `cxp watch` |
|
|
140
|
+
| Install background watcher service | `cxp service install` |
|
|
141
|
+
| Scan for duplicate files | `cxp duplicates scan ~/Downloads` |
|
|
142
|
+
| Check system health and file integrity | `cxp doctor` |
|
|
143
|
+
| View organization history | `cxp history` |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
clutterxp/__init__.py,sha256=NTPQTaRDLLih33Dej5fibtZFRtxjjoW6UC7zGoY583M,83
|
|
2
|
+
clutterxp/interfaces.py,sha256=Zimz4RzcKVeWFkcS5htqTEw2g8eJU4KNK5xjpLbhUT8,4462
|
|
3
|
+
clutterxp/locking.py,sha256=q5z4hcXHyAa7UWSHKkHYOIdv8saenrqzwM_G_lwpC0k,3528
|
|
4
|
+
clutterxp/logging_setup.py,sha256=jPwXjjZMpJJ9qFwN_CRiE8D4refmqXRewgqPG-231Uw,2371
|
|
5
|
+
clutterxp/models.py,sha256=seeIhy48hhMIRBb2wuzBgaHdFNDF9yYKiS8SklKgjKk,8090
|
|
6
|
+
clutterxp/organizer.py,sha256=OXcoY7T025ewjeZ60wEwLJhyiJnecylO-amwicHr_HQ,14466
|
|
7
|
+
clutterxp/paths.py,sha256=oJHZ9UN-CWFbQSuq5h_jY6dZKneWSOO6UTXvMOFAIF8,3685
|
|
8
|
+
clutterxp/planner.py,sha256=QpQciSFYd2M1-q72DMzfmzKmN1XmOCC30J4RoCRTQSs,12509
|
|
9
|
+
clutterxp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
clutterxp/safety.py,sha256=gE9rj2bxyUde8gw3GMy3TKpY78y0qx2Ml6NFV-c1BJk,3483
|
|
11
|
+
clutterxp/scanner.py,sha256=98ku6thFoVPxEjUIWo3jjydtvHQYxfJTyYcpkgYs-1Q,6129
|
|
12
|
+
clutterxp/service_install.py,sha256=7116gxHv7yqLmdYehCXpH0KfFVrhI8rfi-jVSLwrQQM,6566
|
|
13
|
+
clutterxp/units.py,sha256=SKCEUmQVwBsfnDrE5HFetpdI8ry3Isl51RCMTC2v5V4,1918
|
|
14
|
+
clutterxp/cli/__init__.py,sha256=Kz_nwITT56ObyGl0aLJAtFFxtFEU55LlRyEbCVeElsI,197
|
|
15
|
+
clutterxp/cli/app.py,sha256=tLtxvGrY80c5YqW0CwHOK6fIP3D2n33yICuP1NZtpBQ,7144
|
|
16
|
+
clutterxp/cli/cmd_config.py,sha256=N7ge_RrfVT8CD4RESgk_yMBaGJFwCf82nU2Lz0IELbk,9074
|
|
17
|
+
clutterxp/cli/cmd_doctor.py,sha256=VCQHcmiLpXodVhxwMqmo4bPUgAZPUIRwMPu-Aga_6fs,16489
|
|
18
|
+
clutterxp/cli/cmd_duplicates.py,sha256=mXOi2uuimdOiK5IxIqEXF0kHj_fYQsCIvqCYfyOj9cg,8378
|
|
19
|
+
clutterxp/cli/cmd_gui.py,sha256=N9pKrQPgW4zVPUYzjEesfson-3ZKEe6Vau1qhHcx_rk,583
|
|
20
|
+
clutterxp/cli/cmd_history.py,sha256=g6LDRH_otqQsM6FcBCSR-xPazc57lGZMjHH5SMFYT2k,7620
|
|
21
|
+
clutterxp/cli/cmd_init.py,sha256=opEWoDa5_3pdv2lDzwifdyyv17uG4Dr63oyy2bTeLaE,2232
|
|
22
|
+
clutterxp/cli/cmd_organize.py,sha256=zJyZMHd0rFdleoB0T8g3xann-kLT4vzPYKf7U2HotUI,7562
|
|
23
|
+
clutterxp/cli/cmd_rules.py,sha256=IZl7Io343-9r7v59IxqsgStlNLE5-TzVLLDkAKLlXg4,18509
|
|
24
|
+
clutterxp/cli/cmd_scan.py,sha256=m44Qx6OX87lg91UAKvIfc1SfdyVu6MLauFAi19kKoWc,3594
|
|
25
|
+
clutterxp/cli/cmd_service.py,sha256=CL5SvqHiviGcYoNi8mkuWPeJuCBBQyuZ8BLpmRVl0Q8,4119
|
|
26
|
+
clutterxp/cli/cmd_undo.py,sha256=Z0Fvwd5akfTVTbR7KrqpnV4UhoXvdOOCYRkoGgkzIps,5940
|
|
27
|
+
clutterxp/cli/cmd_watch.py,sha256=WWOewgbzn0cbpyBIekuu8E847wk8vu6LSpgT6J9pa9Q,5114
|
|
28
|
+
clutterxp/cli/errors.py,sha256=wB1yZTJdZ7tdQIO1CRcqGWLdX5T2MFvNCKKtAjYMY0c,5206
|
|
29
|
+
clutterxp/cli/render.py,sha256=CKDI7ZAvdvrmqjN8O8y076Aljgk455szIGTvJ6AauRY,13415
|
|
30
|
+
clutterxp/config/__init__.py,sha256=5oqZd6CoBxJPrg1Uw-lt6_xVsotxJa582RVQO5TGAEA,961
|
|
31
|
+
clutterxp/config/jsonschema.py,sha256=s9vG9BU3WIZwViRVmWVrYZWOUU2JBZfOnKjg2tmddk4,294
|
|
32
|
+
clutterxp/config/loader.py,sha256=CIdbrKKskg6ue1bh-Gt5U9Kw0XEHjwHOlpRGSrKQnys,7888
|
|
33
|
+
clutterxp/config/placeholders.py,sha256=7M1SFolDcB-7-2cFJlNMp58MDBBcCR0YoV5bFMTBp40,2466
|
|
34
|
+
clutterxp/config/runtime.py,sha256=MLIs1_kezu9HBvIUaL7VukJMp7Pkv1OWcpE6UAVC7h0,953
|
|
35
|
+
clutterxp/config/schema.py,sha256=1pu4IcJZQT6_OOuCq8zd8Fn6plLNAQMdfSd7c9i3Mfc,6011
|
|
36
|
+
clutterxp/config/validate.py,sha256=w6JnzTdXgHVKTQOnVSpDK1Y1oxHhDa8h_SMcCKVCuzs,9402
|
|
37
|
+
clutterxp/config/writer.py,sha256=KBAKr_AjeUdnS6LjIVzYPTl9_0w1zbe8BKi5HpzvYf0,8254
|
|
38
|
+
clutterxp/duplicates/__init__.py,sha256=i-adUo4yDpNMLisjpbENHrR_zmuDu7SvqPHmhK_4HoM,533
|
|
39
|
+
clutterxp/duplicates/detector.py,sha256=4W1vIrB29futYc8NMp7D3Zise51BQn6fPO_QpM07rQU,16032
|
|
40
|
+
clutterxp/duplicates/hashcache.py,sha256=Di-EmjG0WNjM6TlOiQBiHaI1ex1x_r8x1bqZtsszy-Y,5453
|
|
41
|
+
clutterxp/duplicates/namenorm.py,sha256=nhLopI3RwrJ_BMJtcjxKtoaVDnMuPA9jrE_4rDTfVEc,1392
|
|
42
|
+
clutterxp/duplicates/quarantine.py,sha256=UfTx_KQ-PsnQI9E9iMvdsCLpUcrhF4OONC4E8pP3RfI,5576
|
|
43
|
+
clutterxp/duplicates/report.py,sha256=KMv0D5DmkVzEXVdqAnQ4Byi0JrMJPLUAttNrWJA3JiM,3083
|
|
44
|
+
clutterxp/gui/__init__.py,sha256=7QxwlVXIQEeLiLGnAkJLIk1iUavZzuGBKWeHqwpS3Hs,42
|
|
45
|
+
clutterxp/gui/api.py,sha256=mLzBLj0at6JsJt75Qips8KB_qR7W2D_FOue05z-6KLU,17665
|
|
46
|
+
clutterxp/gui/app.py,sha256=ENE3kbky8sVivfH4rXn8Mww3nPXJ2tqGelw3C3AdFTU,6421
|
|
47
|
+
clutterxp/gui/models_qt.py,sha256=LIqDaE5AznHqEVHRctoXPCSmDHKCFfWR-xx-6v1gepI,17813
|
|
48
|
+
clutterxp/gui/workers.py,sha256=skDvoM-3uOd6u1Tgg1V9hgdnV2xpEteENxRpDk9cMxM,4255
|
|
49
|
+
clutterxp/gui/views/__init__.py,sha256=Izm4c6Zf2AHgEIAUBJUAJSi3rS_fEoATgWgENWbTmqQ,467
|
|
50
|
+
clutterxp/gui/views/dashboard.py,sha256=PG-3jk4MyaCl6hlcsHQkitPaqfjfxzSizXTV79gTEOw,9225
|
|
51
|
+
clutterxp/gui/views/duplicates.py,sha256=oHritzt-KXSTA-vchKGpbcJFEJ7bRgklYFEZ-S7WHr4,15187
|
|
52
|
+
clutterxp/gui/views/folders.py,sha256=vctAOrFCFLsHR8WY7KOkWF1vcOOy5xALH4ZUOsC2KM4,12207
|
|
53
|
+
clutterxp/gui/views/history.py,sha256=vZ3nBt2v-2Oec4la2vP3frrXtpOSeCcoQ7qqnLI4jYM,10099
|
|
54
|
+
clutterxp/gui/views/preview.py,sha256=sfSmg-IRWFNq6D8Hm3Pzs53awvYDWRpADGuTMEcneRw,10348
|
|
55
|
+
clutterxp/gui/views/rules.py,sha256=bW6BdAQuJhdQwDUckw2aS0_b0mfQGvG18D9qA064_zo,19370
|
|
56
|
+
clutterxp/gui/views/settings.py,sha256=mK8qKor-SLXIyRCVkD3z9SjfIv7quRnvY806EuddSV4,6065
|
|
57
|
+
clutterxp/history/__init__.py,sha256=WdRFucyXxETQwkHKkTmlba8r5BVLkXrCYpIl0ZwDzvo,538
|
|
58
|
+
clutterxp/history/db.py,sha256=gCxCWmxxnnZDkozDQDf4UpQcnDsEkHYS73AQVvpH-Cc,5498
|
|
59
|
+
clutterxp/history/pathcodec.py,sha256=vCl7-6HWltKsc78CWjKH_OaA5CjKOSD4Og3osvyQru0,1213
|
|
60
|
+
clutterxp/history/recovery.py,sha256=lpElb_Cp8tsA4L9MOleFPU3VMKZt_Yxs_XrntESyOr8,9712
|
|
61
|
+
clutterxp/history/schema.sql,sha256=t3bSAssLH7VxIUmNjA1KAJKg9KbF9IBX0eRECakukXM,1227
|
|
62
|
+
clutterxp/history/service.py,sha256=LzCicUCAwccZ9p5A5C5GY1ZBRWo6-EuBz2mf1GFvbMs,17902
|
|
63
|
+
clutterxp/history/undo.py,sha256=VefzKrXoX7LzCXTJxFDm5GHKhir6vxBSdlZgwbSwyjc,13360
|
|
64
|
+
clutterxp/mover/__init__.py,sha256=OFdgHyXAXVlWxlFjcEo3eC-m7-5YqCgCyK85jASBYio,571
|
|
65
|
+
clutterxp/mover/collision.py,sha256=BGRY9KgYJubnBlOH_cmr5exns1LYUIOmz3A7rsRb_5E,1469
|
|
66
|
+
clutterxp/mover/fsops.py,sha256=4e5T3tQIK3CPHsJCYAeLVAoK5cQ-M5yOCkSinzpTILI,5303
|
|
67
|
+
clutterxp/mover/safe_move.py,sha256=usP0ow31Z4JT-W65ssRwPGoxNdQUbbHCe2O4-VwDMqw,27382
|
|
68
|
+
clutterxp/rules/__init__.py,sha256=L-IUtlHyqhkEjASeygbYSTj5NVfzeJzXZYUsBxhM4Lo,519
|
|
69
|
+
clutterxp/rules/engine.py,sha256=_7XzJc7glN08Gdmny2nULjgwlwIGqBHwt3LSTarSQW4,14722
|
|
70
|
+
clutterxp/rules/matcher.py,sha256=GnyRs-mLKTT7U1t4RLER35mnJg_9y5Rg2PIXP412zh4,7160
|
|
71
|
+
clutterxp/rules/template.py,sha256=vvWXFHX9wgUd0azUA6FHfpYW_IiqEwrz_0wY7ZUdBfc,5808
|
|
72
|
+
clutterxp/rules/tokenizer.py,sha256=otlEG2ZBvnIXZMOwmRCdmtQJzxOAFOB8ZaBlHXPx_XY,3879
|
|
73
|
+
clutterxp/watch/__init__.py,sha256=38V9Gjgxurl7PmfQbh9SaaIubMPZNGrkLYilS8h7bkg,848
|
|
74
|
+
clutterxp/watch/debounce.py,sha256=fNEZ1O9pMDdoCp5uEWEoZVqZtphhaXhDEgs0rTVDz1I,2538
|
|
75
|
+
clutterxp/watch/events.py,sha256=H4slrysVVf0J4qOZ-0fdQ2AlJDxL7WIivY2651I8NWA,7462
|
|
76
|
+
clutterxp/watch/ignore.py,sha256=TY_jZAQw_JBJQELFBvO1YWOeUVnKaeQbG3w8Ojj3_54,2195
|
|
77
|
+
clutterxp/watch/service.py,sha256=iL_SB-xNokv-rqk7MIxab2YytghRbHjgFBvxxJjKFr8,26064
|
|
78
|
+
clutterxp/watch/settle.py,sha256=mUFG7veiZTjp9Nfb8_SGdju-ZrWV1lBW8M50QMsjBSU,5647
|
|
79
|
+
clutterxp/config.example.yaml,sha256=oRYl2HKG0S7ZqBSJCsolswMEjGfPxdTLM2xG8Ccgq3E,2523
|
|
80
|
+
clutterxp/packaging/clutterxp.service,sha256=f_7idpR_RHNKtyrUCvaIU9EVBvVVB2tyhQL5D4xTUbs,384
|
|
81
|
+
clutterxp-1.1.0.dist-info/METADATA,sha256=wESJZQdHFfDwX_msK1SVCeACmlPGjyridEi0vQUOe4I,5465
|
|
82
|
+
clutterxp-1.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
83
|
+
clutterxp-1.1.0.dist-info/entry_points.txt,sha256=xrAi5zTDu-MJW46ubjRlMsL0r7V3TYfxRTdycFCvmeM,136
|
|
84
|
+
clutterxp-1.1.0.dist-info/RECORD,,
|