137 lines
3.9 KiB
Markdown
137 lines
3.9 KiB
Markdown
# Tagger - Project Documentation
|
|
|
|
**Version:** 1.2.0 | **Status:** Stable | **GUI:** PySide6/Qt6
|
|
|
|
---
|
|
|
|
## About
|
|
|
|
Desktop app for organizing files using hierarchical tags (category/name).
|
|
|
|
**Features:** Folder scanning, tag filtering (AND/OR/NOT), rename/merge tags, CSFD.cz integration
|
|
(with local cache), hardlink structure, 3-level config (global/folder/file), orphaned sidecar detection.
|
|
|
|
---
|
|
|
|
## Structure
|
|
|
|
```
|
|
Tagger/
|
|
├── Tagger.py # Entry point
|
|
├── src/core/ # Business logic (NO UI imports!)
|
|
│ ├── tag.py # Tag value object (immutable)
|
|
│ ├── tag_manager.py # Tag/category management
|
|
│ ├── file.py # File with metadata + CSFD cache
|
|
│ ├── file_manager.py # File management, filtering, orphan detection
|
|
│ ├── config.py # 3-level config system
|
|
│ ├── hardlink_manager.py
|
|
│ ├── csfd.py # CSFD scraper + CSFDMovie serialization
|
|
│ ├── media_utils.py # ffprobe integration
|
|
│ ├── constants.py # APP_NAME, VERSION
|
|
│ └── _version.py # Version fallback for PyInstaller
|
|
├── src/ui/
|
|
│ ├── gui.py # Qt6 GUI (MainWindow)
|
|
│ └── utils.py # load_icon()
|
|
└── tests/ # 274 tests
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Rules
|
|
|
|
1. **UI must not contain business logic** — call FileManager/TagManager
|
|
2. **Core must not import UI** — no PySide6 in src/core/
|
|
3. **Dependency injection** — pass via constructor
|
|
4. **UTF-8 everywhere** — `encoding='utf-8'`, `ensure_ascii=False`
|
|
|
|
---
|
|
|
|
## Config Files
|
|
|
|
| Level | File | Location | Contents |
|
|
|-------|------|----------|----------|
|
|
| Global | `.Tagger.!gtag` | `~/.config/Tagger/` | window geometry, last folder, recent folders |
|
|
| Folder | `.Tagger.!ftag` | project folder | ignore patterns, hardlink settings |
|
|
| File | `.filename.!tag` | same dir as file | tags, date, csfd_url, csfd_cache |
|
|
|
|
---
|
|
|
|
## Key Components
|
|
|
|
**Tag** — immutable, `Tag(category, name)`, `Tag.from_string("cat/name")`
|
|
|
|
**File** — `file_path`, `tags[]`, `date`, `csfd_url`, `csfd_cache`, metadata in `.filename.!tag`
|
|
- `apply_csfd_tags()` — fetch + cache CSFD data
|
|
- `get_cached_movie()` — return CSFDMovie from cache (no network)
|
|
- `set_csfd_url()` — invalidates cache on URL change
|
|
|
|
**TagManager** — `add_tag()`, `get_categories()`, `rename_tag()`, `merge_tag()`
|
|
|
|
**FileManager** — `append(folder)`, `filter_files_by_tags()`, `close_folder()`
|
|
- `on_orphaned_tags` callback — fires when orphaned `.!tag` sidecars are found
|
|
- `find_orphaned_tags()` — manual scan for orphaned sidecars
|
|
|
|
**HardlinkManager** — `create_structure_for_files()`, `sync_structure()`
|
|
|
|
**CSFDMovie** — `to_dict()` / `from_dict()` for cache serialization
|
|
|
|
---
|
|
|
|
## Filtering
|
|
|
|
```python
|
|
# AND (default — all must match)
|
|
fm.filter_files_by_tags(["Žánr/Drama", "Rok/1990"])
|
|
|
|
# OR — at least one must match
|
|
fm.filter_files_by_tags(any_of=["Žánr/Drama", "Žánr/Thriller"])
|
|
|
|
# NOT — none of these
|
|
fm.filter_files_by_tags(must_not=["Stav/Nové"])
|
|
|
|
# Combined
|
|
fm.filter_files_by_tags(
|
|
must_have=["Žánr/Drama"],
|
|
any_of=["Rok/1990", "Rok/1991"],
|
|
must_not=["Stav/Nové"],
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Running
|
|
|
|
```bash
|
|
poetry run python Tagger.py # GUI
|
|
poetry run pytest -q # Tests
|
|
poetry run pyinstaller --onefile Tagger.py # Build
|
|
```
|
|
|
|
---
|
|
|
|
## Shortcuts
|
|
|
|
`Ctrl+O` Open | `Ctrl+T` Tags | `Ctrl+D` Date | `Ctrl+W` Close | `F5` Refresh | `Del` Remove
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
**Version 0.0.0 in build:** Run app once from source to update `_version.py`, then rebuild.
|
|
|
|
**Cannot import:** Use `poetry run python Tagger.py`
|
|
|
|
**Metadata corrupted:** Auto-recovers with defaults.
|
|
|
|
**Config not saved:** Check `~/.config/Tagger/` exists and is writable.
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## Metrics
|
|
|
|
- **Tests:** 274 ✅
|
|
- **Python:** 3.14+
|
|
- **Dependencies:** PySide6, requests, beautifulsoup4, loguru, python-dotenv
|