Architecture Overview
OrcaFile follows a layered architecture with clear separation of concerns using Python's mixin inheritance pattern. The codebase is organized into four distinct layers.
UI Layer
Presentation & styling โ ui/
Handler Layer
Business logic mixins โ handlers/
Worker Layer
Background threads โ workers/
Scanner Layer
NTFS MFT engine โ ntfs_scanner/
Mixin Composition Pattern
The main window uses multiple inheritance to keep handlers modular:
class FileOrganizerApp(ScanHandlersMixin, FilterHandlersMixin, TreeHandlersMixin, QMainWindow):
"""Main application window โ inherits from 3 handler mixins + QMainWindow."""
# MRO: FileOrganizerApp โ ScanHandlersMixin โ FilterHandlersMixin
# โ TreeHandlersMixin โ QMainWindow โ QWidget โ QObject
Project Structure
orcafile_v1.0.2/ โโโ orcafile_main.py # Entry point โโโ orcafile_logo.ico # App icon โโโ check.svg / dash.svg # Checkbox SVG icons โ โโโ ui/ # UI Layer โ โโโ main_window.py # FileOrganizerApp (249 lines) โ โโโ styles.py # Dark & Light QSS (332 lines) โ โโโ theme_manager.py # QSettings persistence (19 lines) โ โโโ handlers/ # Handler Layer โ โโโ scan_handlers.py # Scan lifecycle (117 lines) โ โโโ filter_handlers.py # Extension filtering (69 lines) โ โโโ tree_handlers.py # Tree view & deletion (275 lines) โ โโโ workers/ # Worker Layer โ โโโ scan_worker.py # Background QThread (137 lines) โ โโโ ntfs_scanner/ # Scanner Layer (NEW v1.0.2) โ โโโ volume.py # Raw NTFS volume handle (48 lines) โ โโโ mft.py # USN journal enumeration (108 lines) โ โโโ tree.py # In-memory FS tree (80 lines) โ โโโ navigator.py # Path resolution (118 lines) โ โโโ scanner.py # Orchestrator (141 lines) โ โโโ setup/ # Installer โ โโโ inno_setup.iss # Inno Setup script โ โโโ docs/ # Documentation assets
Core Features
โก Feature 1 โ Drive/Folder Scanning & Extension Grouping
The user selects a folder or drive, and OrcaFile recursively scans the directory tree using a background QThread. In v1.0.2, scanning automatically attempts NTFS MFT reading first for ~10ร faster indexing, falling back to os.walk() on non-NTFS or on failure.
def run(self):
drive_part = os.path.splitdrive(self.target_dir)[0]
drive_letter = drive_part[0] if drive_part else ""
if drive_letter and is_ntfs(drive_letter):
try:
self._run_ntfs(drive_letter) # Fast path
return
except Exception:
pass # fall through to os.walk fallback
self._run_fallback() # Standard os.walk
๐ Feature 2 โ Extension Filter Selection
After scanning, the sidebar shows all discovered extensions with file counts. A smart "accumulative search" lets users build up extension selections across multiple searches without losing previous selections.
๐ Feature 3 โ Open File Location (Double-Click)
Double-clicking any file opens its parent folder in the native file manager. Cross-platform: explorer /select, on Windows, open -R on macOS, xdg-open on Linux.
๐ Feature 4 โ File Name Search
Real-time filtering by file name across all indexed files. Matching files stay visible, empty groups auto-hide. O(n) in-place filtering using QTreeWidgetItem.setHidden().
๐๏ธ Feature 5 โ Multi-Select & Bulk Delete
Tri-state checkboxes (unchecked / checked / partial) for groups and individual files. Selected files can be permanently deleted with confirmation. Internal data structures, filter counts, and tree view all update after deletion.
๐จ Feature 6 โ Dark / Light Theme Toggle
Switch between Nord dark and Snow Storm light themes. Preference is persisted via QSettings (Windows Registry at HKCU\Software\OrcaFile\Preferences). Two complete QSS stylesheets (~160 lines each) define every widget.
๐ Feature 7 โ File Size Display & Sorting
Every file now shows its size in a dedicated third column. Extension groups display their total size. Click the "Size โผ" button to toggle ascending/descending sort order. Both groups and files within groups are sorted by size.
def format_size(size_bytes: int) -> str:
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 ** 2:
return f"{size_bytes / 1024:.1f} KB"
elif size_bytes < 1024 ** 3:
return f"{size_bytes / (1024 ** 2):.1f} MB"
else:
return f"{size_bytes / (1024 ** 3):.2f} GB"
def toggle_sort_order(self):
self._sort_ascending = not self._sort_ascending
self.sort_order_btn.setText("Size โฒ" if self._sort_ascending else "Size โผ")
self.populate_tree_view()
๐พ Feature 8 โ NTFS MFT Scanner
A custom NTFS scanner that reads the Master File Table directly via the Windows USN Change Journal API (FSCTL_ENUM_USN_DATA). This bypasses the filesystem layer entirely, achieving ~10ร faster scanning on large NTFS volumes. Requires administrator privileges.
def scan_ntfs(drive_letter: str) -> ScanResult:
handle = open_volume(drive_letter)
try:
records = enum_mft_records(handle) # Generator of USN records
nodes, children = build_tree(records) # Build in-memory tree
finally:
close_volume(handle)
rollup_sizes(nodes, children) # Compute recursive sizes
return ScanResult(drive_letter, nodes, children, ...)
The scanner pipeline: Volume Handle โ USN Enumeration โ Tree Build โ Size Rollup โ Path Navigation โ Extension Grouping
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| Language | Python 3.10+ | Core application logic |
| GUI | PyQt6 | Widgets, event loop, threading |
| NTFS Scanner | pywin32 (win32file) | Raw volume I/O & USN journal v1.0.2 |
| Threading | QThread | Non-blocking file scanning |
| Persistence | QSettings | Theme preference (Windows Registry) |
| Styling | QSS (Qt Style Sheets) | CSS-like styling for all widgets |
| Build | PyInstaller | Python โ standalone .exe |
| Installer | Inno Setup | Windows installer wizard |
| License | GPLv3 | Open source license |
Data Structures & State
Central Data Store: self.all_data
The entire application revolves around a single dictionary:
# Type: dict[str, list[tuple[str, str, int]]]
self.all_data = {
".pdf": [
("report.pdf", "C:\\Users\\docs\\report.pdf", 245760),
("invoice.pdf", "D:\\archive\\invoice.pdf", 102400),
],
".jpg": [
("photo.jpg", "E:\\images\\photo.jpg", 3145728),
],
"no extension": [
("Makefile", "C:\\project\\Makefile", 1024),
],
}
# Tuple: (filename, full_absolute_path, size_in_bytes)
State Variables
# Core state
self.all_data = {} # Extension โ file list
self.worker = None # Current ScanWorker thread
self._current_theme = "dark" # Active theme
# Accumulative search state
self.first_search_done = False # Has user ever searched?
self.solidified_selections = set() # Saved extension selections
self._last_search_was_empty = True # Was search bar empty?
# v1.0.2 additions
self._sort_ascending = False # Size sort direction
self._block_tree_signals = False # Prevents checkbox recursion
Scan Pipeline
Data Flow
User Input Main Thread Worker Thread
โโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโ
path_input.text() โโโโโโโโโโบ start_scan()
โ
โโโ validate path
โโโ reset UI
โโโ create ScanWorker โโโโโโโโบ .start()
โ โ
โ [NTFS? โ _run_ntfs()]
โ [else โ _run_fallback()]
โ โ
โ status signal โโโโโโโโโโ "READING MFT..."
โ progress signal โโโโโโโโโ (100, 5000)
โ finished signal โโโโโโโโโ {ext: [(name, path, size)]}
โ
โโโ handle_scan_results()
โโโ populate filter_list
โโโ populate_tree_view()
Thread safety: All UI updates happen on the main thread via Qt's signal-slot mechanism. pyqtSignal instances serialize data across thread boundaries. The worker never directly touches any widget.
NTFS MFT Scanner NEW v1.0.2
Why MFT Scanning?
os.walk() traverses the directory tree via the filesystem layer, issuing thousands of syscalls. The NTFS MFT scanner reads the Master File Table directly using DeviceIoControl(FSCTL_ENUM_USN_DATA), fetching all records in bulk. This achieves ~10ร faster scanning on large volumes (500K+ files).
volume.py
Opens raw NTFS volume handle via CreateFile("\\.\C:"). Detects NTFS via GetVolumeInformation().
mft.py
Enumerates USN records via FSCTL_ENUM_USN_DATA. Parses USN_RECORD_V2 structs for FRN, parent, size, and name.
tree.py
Builds in-memory directory tree from MFT records. FsNode dataclass stores name, size, is_dir. Iterative post-order DFS for size rollup.
navigator.py
Resolves paths to FRNs, lists folder contents sorted by size. Pure in-memory โ no disk access after initial MFT read.
USN Record Parsing
FSCTL_ENUM_USN_DATA = 0x900B3 # Control code for USN enumeration
FILE_ATTRIBUTE_DIRECTORY = 0x10 # Directory flag in file attributes
NTFS_ROOT_FRN = 5 # Root directory always has FRN 5
OUTPUT_BUFFER_SIZE = 65536 # 64KB per DeviceIoControl call
def _parse_usn_record(raw: bytes, offset: int) -> tuple:
rec_len = struct.unpack_from("I", raw, offset)[0]
frn = struct.unpack_from("Q", raw, offset + 8)[0]
parent_frn = struct.unpack_from("Q", raw, offset + 16)[0]
file_attrs = struct.unpack_from("I", raw, offset + 52)[0]
file_size = struct.unpack_from("Q", raw, offset + 56)[0]
name_len = struct.unpack_from("H", raw, offset + 60)[0]
name_off = struct.unpack_from("H", raw, offset + 62)[0]
name = raw[offset+name_off : offset+name_off+name_len].decode("utf-16-le")
return rec_len, frn, parent_frn, file_size, file_attrs, name
Build & Distribution
Build Pipeline
Source Code PyInstaller Inno Setup
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโ
orcafile_main.py โโ
ui/*.py โโโโบ pyinstaller โโโบ dist/orcafile_main/ โโโบ inno_setup.iss
handlers/*.py โ .spec โโโ orcafile_main.exe โ
workers/*.py โ โโโ python310.dll โผ
ntfs_scanner/*.py โ โโโ PyQt6/ orcafile_setup.exe
*.svg, *.ico (Windows installer)
pyinstaller --noconsole --icon=orcafile_logo.ico orcafile_main.py creates a Windows GUI app. Inno Setup (setup/inno_setup.iss) packages it into a professional installer with Start Menu and Desktop shortcuts.
Cross-Platform Support
| Concern | Implementation |
|---|---|
| Taskbar icon | Windows-only SetCurrentProcessExplicitAppUserModelID() via ctypes |
| File manager | explorer /select, ยท open -R ยท xdg-open |
| Theme persistence | QSettings: Windows Registry / ~/.config / ~/Library/Preferences |
| NTFS scanner | Windows only (pywin32). Falls back to os.walk() on macOS/Linux |
| Path handling | os.path.join(), os.path.normpath() โ platform-aware |
| Dependencies | PyQt6 (all platforms) + pywin32 (Windows only, optional) |