je-file-tree 0.1.1__tar.gz
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.
- je_file_tree-0.1.1/LICENSE +21 -0
- je_file_tree-0.1.1/PKG-INFO +177 -0
- je_file_tree-0.1.1/README.md +146 -0
- je_file_tree-0.1.1/je_file_tree/__init__.py +7 -0
- je_file_tree-0.1.1/je_file_tree/__main__.py +6 -0
- je_file_tree-0.1.1/je_file_tree/core/__init__.py +1 -0
- je_file_tree-0.1.1/je_file_tree/core/allocation.py +129 -0
- je_file_tree-0.1.1/je_file_tree/core/analysis.py +237 -0
- je_file_tree-0.1.1/je_file_tree/core/compare.py +131 -0
- je_file_tree-0.1.1/je_file_tree/core/duplicates.py +186 -0
- je_file_tree-0.1.1/je_file_tree/core/export.py +118 -0
- je_file_tree-0.1.1/je_file_tree/core/formatting.py +61 -0
- je_file_tree-0.1.1/je_file_tree/core/node.py +150 -0
- je_file_tree-0.1.1/je_file_tree/core/scanner.py +356 -0
- je_file_tree-0.1.1/je_file_tree/core/search.py +69 -0
- je_file_tree-0.1.1/je_file_tree/core/sunburst.py +58 -0
- je_file_tree-0.1.1/je_file_tree/core/treemap.py +179 -0
- je_file_tree-0.1.1/je_file_tree/gui/__init__.py +1 -0
- je_file_tree-0.1.1/je_file_tree/gui/app.py +60 -0
- je_file_tree-0.1.1/je_file_tree/gui/bar_chart.py +218 -0
- je_file_tree-0.1.1/je_file_tree/gui/changes_panel.py +148 -0
- je_file_tree-0.1.1/je_file_tree/gui/charts.py +102 -0
- je_file_tree-0.1.1/je_file_tree/gui/delegates.py +40 -0
- je_file_tree-0.1.1/je_file_tree/gui/duplicates_panel.py +282 -0
- je_file_tree-0.1.1/je_file_tree/gui/elevation.py +84 -0
- je_file_tree-0.1.1/je_file_tree/gui/file_actions.py +78 -0
- je_file_tree-0.1.1/je_file_tree/gui/help_dialog.py +25 -0
- je_file_tree-0.1.1/je_file_tree/gui/i18n.py +57 -0
- je_file_tree-0.1.1/je_file_tree/gui/icon.py +86 -0
- je_file_tree-0.1.1/je_file_tree/gui/main_window.py +604 -0
- je_file_tree-0.1.1/je_file_tree/gui/qt_translation.py +27 -0
- je_file_tree-0.1.1/je_file_tree/gui/reasons.py +15 -0
- je_file_tree-0.1.1/je_file_tree/gui/results_view.py +790 -0
- je_file_tree-0.1.1/je_file_tree/gui/scan_bar.py +95 -0
- je_file_tree-0.1.1/je_file_tree/gui/scan_worker.py +213 -0
- je_file_tree-0.1.1/je_file_tree/gui/search_panel.py +123 -0
- je_file_tree-0.1.1/je_file_tree/gui/strings.py +839 -0
- je_file_tree-0.1.1/je_file_tree/gui/sunburst_widget.py +271 -0
- je_file_tree-0.1.1/je_file_tree/gui/tables.py +259 -0
- je_file_tree-0.1.1/je_file_tree/gui/tree_model.py +349 -0
- je_file_tree-0.1.1/je_file_tree/gui/treemap_widget.py +288 -0
- je_file_tree-0.1.1/je_file_tree/gui/welcome.py +149 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/PKG-INFO +177 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/SOURCES.txt +67 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/dependency_links.txt +1 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/entry_points.txt +2 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/requires.txt +1 -0
- je_file_tree-0.1.1/je_file_tree.egg-info/top_level.txt +1 -0
- je_file_tree-0.1.1/pyproject.toml +73 -0
- je_file_tree-0.1.1/setup.cfg +4 -0
- je_file_tree-0.1.1/test/test_allocation.py +81 -0
- je_file_tree-0.1.1/test/test_build_nuitka.py +61 -0
- je_file_tree-0.1.1/test/test_compare.py +85 -0
- je_file_tree-0.1.1/test/test_core.py +276 -0
- je_file_tree-0.1.1/test/test_duplicates.py +88 -0
- je_file_tree-0.1.1/test/test_elevation.py +142 -0
- je_file_tree-0.1.1/test/test_gui.py +690 -0
- je_file_tree-0.1.1/test/test_i18n.py +99 -0
- je_file_tree-0.1.1/test/test_icon.py +76 -0
- je_file_tree-0.1.1/test/test_layers.py +28 -0
- je_file_tree-0.1.1/test/test_readme_parity.py +72 -0
- je_file_tree-0.1.1/test/test_release.py +90 -0
- je_file_tree-0.1.1/test/test_scanner.py +194 -0
- je_file_tree-0.1.1/test/test_search.py +50 -0
- je_file_tree-0.1.1/test/test_start_script.py +19 -0
- je_file_tree-0.1.1/test/test_sunburst.py +45 -0
- je_file_tree-0.1.1/test/test_treemap.py +96 -0
- je_file_tree-0.1.1/test/test_workflow_actions.py +124 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JE-Chen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: je_file_tree
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: See where your disk space goes: a fast folder-size scanner with a tree, a treemap and a list of the largest files.
|
|
5
|
+
Author-email: JE-Chen <jechenmailman@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JeffreyChen-s-Utils/FileTree
|
|
8
|
+
Project-URL: Code, https://github.com/JeffreyChen-s-Utils/FileTree
|
|
9
|
+
Keywords: disk-usage,disk-space,folder-size,treemap,treesize,pyside6,qt
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
17
|
+
Classifier: Environment :: MacOS X
|
|
18
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
19
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
20
|
+
Classifier: Natural Language :: English
|
|
21
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
22
|
+
Classifier: Natural Language :: Chinese (Traditional)
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Classifier: Topic :: System :: Filesystems
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: PySide6>=6.8
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# FileTree
|
|
33
|
+
|
|
34
|
+
**See where your disk space goes.** FileTree scans a folder or a whole drive, adds up every file inside
|
|
35
|
+
it, and shows you the biggest folders and files first — in a folder tree, a colourful treemap and a list
|
|
36
|
+
of the largest files. When you find something you no longer need, move it to the Recycle Bin right from
|
|
37
|
+
the window.
|
|
38
|
+
|
|
39
|
+
[English](README.md) | [繁體中文](README/README_zh-TW.md) | [简体中文](README/README_zh-CN.md)
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **One click to start**: pick a folder, click a drive, drag a folder onto the window, or paste a path.
|
|
46
|
+
- **Fast, and live**: several folders are read at once; about 740,000 files and folders are scanned in
|
|
47
|
+
6–8 seconds on an SSD. The tree fills in while the scan runs, biggest folders first, and Stop keeps
|
|
48
|
+
what was read so far.
|
|
49
|
+
- **Folder tree** sorted largest first, with the space each entry takes on disk, a bar showing its share of
|
|
50
|
+
its folder, file and folder counts, and the last change inside it.
|
|
51
|
+
- **Chart**, three ways to see a folder, switched in the corner of the tab: the **treemap** (every file a
|
|
52
|
+
rectangle sized by the space it takes), **bars** (one bar per entry of the folder, largest first, with
|
|
53
|
+
its size and share: easiest to read exactly) and the **sunburst** (the folder in the centre, each deeper
|
|
54
|
+
level a ring: the whole hierarchy at a glance). Click to find an entry in the tree, double-click to go
|
|
55
|
+
into a folder; all three follow.
|
|
56
|
+
- **Largest files**: the 1,000 biggest files anywhere in the scan, with a filter box.
|
|
57
|
+
- **Search** (Ctrl+F): find files and folders by name, or by a pattern such as `*.mp4`, anywhere in the scan.
|
|
58
|
+
- **Duplicates**: files with the same content, grouped, with the space the extra copies take; select the
|
|
59
|
+
extra copies with one click and move them to the Recycle Bin.
|
|
60
|
+
- **Compare with an earlier scan**: save a scan as JSON, and later see which folders grew, shrank,
|
|
61
|
+
appeared or disappeared since.
|
|
62
|
+
- **File types** and **Age**: space used per extension and kind (pictures, videos, archives…) and by when
|
|
63
|
+
files last changed; double-click a row to list its largest files.
|
|
64
|
+
- **Free space safely**: *Move to Recycle Bin* always asks first and never deletes permanently; the
|
|
65
|
+
numbers update immediately, without a rescan.
|
|
66
|
+
- **Export** the folder list or the largest files to CSV (opens in Excel), or the folder tree to JSON.
|
|
67
|
+
- **English, 繁體中文 and 简体中文**, switchable at any time; a built-in *How to use* guide.
|
|
68
|
+
- Links and junctions are listed but never followed, so nothing is counted twice and a link loop cannot
|
|
69
|
+
trap a scan. Folders that cannot be read are listed under *Problems* instead of stopping the scan.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
FileTree needs Python 3.10 or newer. It runs on Windows, macOS and Linux.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install git+https://github.com/JeffreyChen-s-Utils/FileTree.git
|
|
77
|
+
je-file-tree
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or run it from a copy of the source:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/JeffreyChen-s-Utils/FileTree.git
|
|
84
|
+
cd FileTree
|
|
85
|
+
pip install -r requirements.txt
|
|
86
|
+
python start_file_tree.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`python -m je_file_tree` does the same.
|
|
90
|
+
|
|
91
|
+
### Build a stand-alone program
|
|
92
|
+
|
|
93
|
+
To give FileTree to someone without Python, compile it with Nuitka into a program folder or a single
|
|
94
|
+
`.exe`: see [nuitka.md](nuitka.md) for the commands and what each option does.
|
|
95
|
+
|
|
96
|
+
## How to use
|
|
97
|
+
|
|
98
|
+
1. **Choose what to scan**: click *Choose a folder…* or one of the drives on the start page, drag a
|
|
99
|
+
folder onto the window, or type a path in the box at the top and press Enter. You can also start a
|
|
100
|
+
scan from the command line: `je-file-tree D:\Projects` (or `python start_file_tree.py D:\Projects`).
|
|
101
|
+
2. **Watch it fill in**: the tree appears right away and the biggest folders move to the top while
|
|
102
|
+
FileTree works; the largest files and file types follow when the scan ends. *Stop* (or Esc) ends the
|
|
103
|
+
scan at any time and keeps what was read so far, marked as incomplete.
|
|
104
|
+
3. **Find what takes the space**: the biggest folders are at the top of the tree. Open a folder with the
|
|
105
|
+
arrow next to it, or explore the chart on the right.
|
|
106
|
+
|
|
107
|
+
### Reading the results
|
|
108
|
+
|
|
109
|
+
| Where | What it tells you |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Folder tree | Size, *On disk* (the space really taken: whole clusters, so usually a little more; less for compressed files, nothing for files kept only online), *% of parent* (the share of the folder above), number of files and folders inside, last change |
|
|
112
|
+
| Chart | *Treemap*: one rectangle per file, sized by space used; each folder has a strip with its name and size, and tiles show their size. *Levels* sets how many levels are drawn (2 at first, up to all), *Colours* colours by file type (the legend is under it) or by top-level folder. *Bars*: one bar per entry of the folder shown, largest first, with its size and share of the folder. *Sunburst*: the folder in the centre and each deeper level as a ring, the angles by size, each top-level folder in its own colour; click the centre to go up. Double-click a folder to go into it and *Up* to go back; the three views always show the same folder, and FileTree remembers which one you chose |
|
|
113
|
+
| Largest files | The 1,000 biggest files; type in the filter box to narrow the list, double-click to find a file in the tree |
|
|
114
|
+
| Search | Files and folders whose name contains what you type; a pattern (`*.mp4`) must match the whole name, several are separated by `;` (`*.iso;*.zip`); the 1,000 largest matches are listed with the count and total size of all |
|
|
115
|
+
| Duplicates | Press *Find duplicates*: files of the same size are compared, first by their first 64 KB, then by their whole content (hard links count once). Files under 1 MB are left out unless you choose a smaller size, because reading takes time. Each group lists its copies oldest first; *Select extra copies* selects all but the oldest, ready for Delete |
|
|
116
|
+
| File types | Space per extension; choose a kind above the table to see only that kind, double-click a row to list the largest files of that type |
|
|
117
|
+
| Age | Space by when files last changed (within a month … over two years ago); double-click a row to list its largest files |
|
|
118
|
+
| Problems | Folders FileTree was not allowed to read; their contents are not counted |
|
|
119
|
+
|
|
120
|
+
### Freeing space
|
|
121
|
+
|
|
122
|
+
Right-click any entry to open it, show it in your file manager, copy its path, show it in the chart,
|
|
123
|
+
rescan that folder after changes made outside FileTree (the rest of the results stay), scan that folder on
|
|
124
|
+
its own, or move it to the Recycle Bin (the Trash on macOS and Linux). To move several entries at once,
|
|
125
|
+
pick them with Ctrl+click or Shift+click in the folder tree, the Largest files list or the search results:
|
|
126
|
+
FileTree asks once, listing them with their total size. It always asks before moving anything and never
|
|
127
|
+
deletes permanently.
|
|
128
|
+
|
|
129
|
+
### Seeing what grew
|
|
130
|
+
|
|
131
|
+
Save a scan with **File → Export → Folder tree (JSON)**. Later, after a new scan, choose **File → Compare with a
|
|
132
|
+
saved scan…** and open that file: a **Changes** tab lists every folder that changed, with its size then and
|
|
133
|
+
now, the biggest growth first; new folders say *new* and removed ones *gone*. The comparison follows further
|
|
134
|
+
rescans until you press *Stop comparing*. Folders are matched by their path below the scanned folder, so a
|
|
135
|
+
scan can also be compared with a copy of the same tree elsewhere, such as a backup.
|
|
136
|
+
|
|
137
|
+
### Keyboard shortcuts
|
|
138
|
+
|
|
139
|
+
| Key | Action |
|
|
140
|
+
|---|---|
|
|
141
|
+
| Ctrl+O | Choose a folder |
|
|
142
|
+
| F5 | Rescan |
|
|
143
|
+
| Esc | Stop the scan |
|
|
144
|
+
| Ctrl+F | Search by name |
|
|
145
|
+
| Delete | Move the selected entries to the Recycle Bin |
|
|
146
|
+
| F1 | How to use |
|
|
147
|
+
| Ctrl+Q | Quit |
|
|
148
|
+
|
|
149
|
+
On macOS use ⌘ instead of Ctrl (⌘R rescans).
|
|
150
|
+
|
|
151
|
+
### Good to know
|
|
152
|
+
|
|
153
|
+
- Sizes are real file sizes in binary units (1 KB = 1,024 bytes), the same as Windows Explorer. Pick a
|
|
154
|
+
fixed unit under *View → Size unit*.
|
|
155
|
+
- On Windows, FileTree asks for administrator rights when it starts, like TreeSize, so it can read protected
|
|
156
|
+
folders too. Say no and it runs normally; folders it could not read are listed under *Problems*, with a
|
|
157
|
+
*Restart as administrator* button (also in the *File* menu). Turn the question off under
|
|
158
|
+
*View → Ask for administrator rights at start*.
|
|
159
|
+
- Hidden files are counted. Turn off *View → Include hidden files* to leave them out of the next scan.
|
|
160
|
+
- Your language, size unit, window layout and recently scanned folders are remembered (on Windows in the
|
|
161
|
+
registry under `HKEY_CURRENT_USER\Software\JE-Chen\FileTree`).
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pip install -r dev_requirements.txt
|
|
167
|
+
python -m pytest
|
|
168
|
+
python -m ruff check .
|
|
169
|
+
python tools/make_screenshots.py
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`tools/make_screenshots.py` redraws the README pictures from a made-up folder, in every language. The
|
|
173
|
+
code layout, the main flows and the design rules are described in [architecture.md](architecture.md).
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# FileTree
|
|
2
|
+
|
|
3
|
+
**See where your disk space goes.** FileTree scans a folder or a whole drive, adds up every file inside
|
|
4
|
+
it, and shows you the biggest folders and files first — in a folder tree, a colourful treemap and a list
|
|
5
|
+
of the largest files. When you find something you no longer need, move it to the Recycle Bin right from
|
|
6
|
+
the window.
|
|
7
|
+
|
|
8
|
+
[English](README.md) | [繁體中文](README/README_zh-TW.md) | [简体中文](README/README_zh-CN.md)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **One click to start**: pick a folder, click a drive, drag a folder onto the window, or paste a path.
|
|
15
|
+
- **Fast, and live**: several folders are read at once; about 740,000 files and folders are scanned in
|
|
16
|
+
6–8 seconds on an SSD. The tree fills in while the scan runs, biggest folders first, and Stop keeps
|
|
17
|
+
what was read so far.
|
|
18
|
+
- **Folder tree** sorted largest first, with the space each entry takes on disk, a bar showing its share of
|
|
19
|
+
its folder, file and folder counts, and the last change inside it.
|
|
20
|
+
- **Chart**, three ways to see a folder, switched in the corner of the tab: the **treemap** (every file a
|
|
21
|
+
rectangle sized by the space it takes), **bars** (one bar per entry of the folder, largest first, with
|
|
22
|
+
its size and share: easiest to read exactly) and the **sunburst** (the folder in the centre, each deeper
|
|
23
|
+
level a ring: the whole hierarchy at a glance). Click to find an entry in the tree, double-click to go
|
|
24
|
+
into a folder; all three follow.
|
|
25
|
+
- **Largest files**: the 1,000 biggest files anywhere in the scan, with a filter box.
|
|
26
|
+
- **Search** (Ctrl+F): find files and folders by name, or by a pattern such as `*.mp4`, anywhere in the scan.
|
|
27
|
+
- **Duplicates**: files with the same content, grouped, with the space the extra copies take; select the
|
|
28
|
+
extra copies with one click and move them to the Recycle Bin.
|
|
29
|
+
- **Compare with an earlier scan**: save a scan as JSON, and later see which folders grew, shrank,
|
|
30
|
+
appeared or disappeared since.
|
|
31
|
+
- **File types** and **Age**: space used per extension and kind (pictures, videos, archives…) and by when
|
|
32
|
+
files last changed; double-click a row to list its largest files.
|
|
33
|
+
- **Free space safely**: *Move to Recycle Bin* always asks first and never deletes permanently; the
|
|
34
|
+
numbers update immediately, without a rescan.
|
|
35
|
+
- **Export** the folder list or the largest files to CSV (opens in Excel), or the folder tree to JSON.
|
|
36
|
+
- **English, 繁體中文 and 简体中文**, switchable at any time; a built-in *How to use* guide.
|
|
37
|
+
- Links and junctions are listed but never followed, so nothing is counted twice and a link loop cannot
|
|
38
|
+
trap a scan. Folders that cannot be read are listed under *Problems* instead of stopping the scan.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
FileTree needs Python 3.10 or newer. It runs on Windows, macOS and Linux.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install git+https://github.com/JeffreyChen-s-Utils/FileTree.git
|
|
46
|
+
je-file-tree
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or run it from a copy of the source:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/JeffreyChen-s-Utils/FileTree.git
|
|
53
|
+
cd FileTree
|
|
54
|
+
pip install -r requirements.txt
|
|
55
|
+
python start_file_tree.py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`python -m je_file_tree` does the same.
|
|
59
|
+
|
|
60
|
+
### Build a stand-alone program
|
|
61
|
+
|
|
62
|
+
To give FileTree to someone without Python, compile it with Nuitka into a program folder or a single
|
|
63
|
+
`.exe`: see [nuitka.md](nuitka.md) for the commands and what each option does.
|
|
64
|
+
|
|
65
|
+
## How to use
|
|
66
|
+
|
|
67
|
+
1. **Choose what to scan**: click *Choose a folder…* or one of the drives on the start page, drag a
|
|
68
|
+
folder onto the window, or type a path in the box at the top and press Enter. You can also start a
|
|
69
|
+
scan from the command line: `je-file-tree D:\Projects` (or `python start_file_tree.py D:\Projects`).
|
|
70
|
+
2. **Watch it fill in**: the tree appears right away and the biggest folders move to the top while
|
|
71
|
+
FileTree works; the largest files and file types follow when the scan ends. *Stop* (or Esc) ends the
|
|
72
|
+
scan at any time and keeps what was read so far, marked as incomplete.
|
|
73
|
+
3. **Find what takes the space**: the biggest folders are at the top of the tree. Open a folder with the
|
|
74
|
+
arrow next to it, or explore the chart on the right.
|
|
75
|
+
|
|
76
|
+
### Reading the results
|
|
77
|
+
|
|
78
|
+
| Where | What it tells you |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Folder tree | Size, *On disk* (the space really taken: whole clusters, so usually a little more; less for compressed files, nothing for files kept only online), *% of parent* (the share of the folder above), number of files and folders inside, last change |
|
|
81
|
+
| Chart | *Treemap*: one rectangle per file, sized by space used; each folder has a strip with its name and size, and tiles show their size. *Levels* sets how many levels are drawn (2 at first, up to all), *Colours* colours by file type (the legend is under it) or by top-level folder. *Bars*: one bar per entry of the folder shown, largest first, with its size and share of the folder. *Sunburst*: the folder in the centre and each deeper level as a ring, the angles by size, each top-level folder in its own colour; click the centre to go up. Double-click a folder to go into it and *Up* to go back; the three views always show the same folder, and FileTree remembers which one you chose |
|
|
82
|
+
| Largest files | The 1,000 biggest files; type in the filter box to narrow the list, double-click to find a file in the tree |
|
|
83
|
+
| Search | Files and folders whose name contains what you type; a pattern (`*.mp4`) must match the whole name, several are separated by `;` (`*.iso;*.zip`); the 1,000 largest matches are listed with the count and total size of all |
|
|
84
|
+
| Duplicates | Press *Find duplicates*: files of the same size are compared, first by their first 64 KB, then by their whole content (hard links count once). Files under 1 MB are left out unless you choose a smaller size, because reading takes time. Each group lists its copies oldest first; *Select extra copies* selects all but the oldest, ready for Delete |
|
|
85
|
+
| File types | Space per extension; choose a kind above the table to see only that kind, double-click a row to list the largest files of that type |
|
|
86
|
+
| Age | Space by when files last changed (within a month … over two years ago); double-click a row to list its largest files |
|
|
87
|
+
| Problems | Folders FileTree was not allowed to read; their contents are not counted |
|
|
88
|
+
|
|
89
|
+
### Freeing space
|
|
90
|
+
|
|
91
|
+
Right-click any entry to open it, show it in your file manager, copy its path, show it in the chart,
|
|
92
|
+
rescan that folder after changes made outside FileTree (the rest of the results stay), scan that folder on
|
|
93
|
+
its own, or move it to the Recycle Bin (the Trash on macOS and Linux). To move several entries at once,
|
|
94
|
+
pick them with Ctrl+click or Shift+click in the folder tree, the Largest files list or the search results:
|
|
95
|
+
FileTree asks once, listing them with their total size. It always asks before moving anything and never
|
|
96
|
+
deletes permanently.
|
|
97
|
+
|
|
98
|
+
### Seeing what grew
|
|
99
|
+
|
|
100
|
+
Save a scan with **File → Export → Folder tree (JSON)**. Later, after a new scan, choose **File → Compare with a
|
|
101
|
+
saved scan…** and open that file: a **Changes** tab lists every folder that changed, with its size then and
|
|
102
|
+
now, the biggest growth first; new folders say *new* and removed ones *gone*. The comparison follows further
|
|
103
|
+
rescans until you press *Stop comparing*. Folders are matched by their path below the scanned folder, so a
|
|
104
|
+
scan can also be compared with a copy of the same tree elsewhere, such as a backup.
|
|
105
|
+
|
|
106
|
+
### Keyboard shortcuts
|
|
107
|
+
|
|
108
|
+
| Key | Action |
|
|
109
|
+
|---|---|
|
|
110
|
+
| Ctrl+O | Choose a folder |
|
|
111
|
+
| F5 | Rescan |
|
|
112
|
+
| Esc | Stop the scan |
|
|
113
|
+
| Ctrl+F | Search by name |
|
|
114
|
+
| Delete | Move the selected entries to the Recycle Bin |
|
|
115
|
+
| F1 | How to use |
|
|
116
|
+
| Ctrl+Q | Quit |
|
|
117
|
+
|
|
118
|
+
On macOS use ⌘ instead of Ctrl (⌘R rescans).
|
|
119
|
+
|
|
120
|
+
### Good to know
|
|
121
|
+
|
|
122
|
+
- Sizes are real file sizes in binary units (1 KB = 1,024 bytes), the same as Windows Explorer. Pick a
|
|
123
|
+
fixed unit under *View → Size unit*.
|
|
124
|
+
- On Windows, FileTree asks for administrator rights when it starts, like TreeSize, so it can read protected
|
|
125
|
+
folders too. Say no and it runs normally; folders it could not read are listed under *Problems*, with a
|
|
126
|
+
*Restart as administrator* button (also in the *File* menu). Turn the question off under
|
|
127
|
+
*View → Ask for administrator rights at start*.
|
|
128
|
+
- Hidden files are counted. Turn off *View → Include hidden files* to leave them out of the next scan.
|
|
129
|
+
- Your language, size unit, window layout and recently scanned folders are remembered (on Windows in the
|
|
130
|
+
registry under `HKEY_CURRENT_USER\Software\JE-Chen\FileTree`).
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install -r dev_requirements.txt
|
|
136
|
+
python -m pytest
|
|
137
|
+
python -m ruff check .
|
|
138
|
+
python tools/make_screenshots.py
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`tools/make_screenshots.py` redraws the README pictures from a made-up folder, in every language. The
|
|
142
|
+
code layout, the main flows and the design rules are described in [architecture.md](architecture.md).
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Scanning and analysis, independent of any GUI toolkit."""
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""The space a file takes on disk ("size on disk"), which can differ from its size.
|
|
2
|
+
|
|
3
|
+
A file takes whole clusters, so it usually takes a little more than its size; a compressed or
|
|
4
|
+
sparse file can take much less, and a file whose data is elsewhere (offline, or a cloud file kept
|
|
5
|
+
online only, such as a OneDrive placeholder) takes none.
|
|
6
|
+
|
|
7
|
+
- POSIX: ``st_blocks`` (512-byte units) is exact and comes with the ``stat`` the scan makes anyway.
|
|
8
|
+
- Windows: a directory listing has no allocation, and asking for it file by file
|
|
9
|
+
(``GetCompressedFileSizeW``) took 100-145 µs per file (measured 2026-09-26 on 60,000 files of D:\\Codes),
|
|
10
|
+
several times what the whole scan spends on a file. So an ordinary file is rounded up to whole
|
|
11
|
+
clusters, which came within 0.64 % of the exact total on 765,000 entries (tiny files stored inside
|
|
12
|
+
the MFT take no cluster of their own); only compressed and sparse files are asked for, and a file
|
|
13
|
+
whose data is elsewhere counts 0 without being opened, so nothing is downloaded.
|
|
14
|
+
|
|
15
|
+
Cost, measured 2026-09-26 on 766,000 entries of D:\\Codes (median of five scans each): the scan takes
|
|
16
|
+
about 3 % longer (6.93 s to 7.12 s) and each entry 17.5 bytes more. Small sizes are handed out from a
|
|
17
|
+
table (``_SHARED``) so files of the same few clusters share one int object; without it each entry grew
|
|
18
|
+
by 44 bytes and the scan by 5-9 %.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import ctypes
|
|
24
|
+
import functools
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
Allocation = Callable[["os.DirEntry[str]", os.stat_result], int]
|
|
31
|
+
"""Tells the space a file takes on disk from its directory entry and its ``stat`` result."""
|
|
32
|
+
|
|
33
|
+
DEFAULT_CLUSTER = 4096
|
|
34
|
+
|
|
35
|
+
_SPARSE = 0x200
|
|
36
|
+
_COMPRESSED = 0x800
|
|
37
|
+
_OFFLINE = 0x1000
|
|
38
|
+
_RECALL_ON_OPEN = 0x40000
|
|
39
|
+
_RECALL_ON_DATA_ACCESS = 0x400000
|
|
40
|
+
_PACKED = _SPARSE | _COMPRESSED
|
|
41
|
+
_ELSEWHERE = _OFFLINE | _RECALL_ON_OPEN | _RECALL_ON_DATA_ACCESS
|
|
42
|
+
_UNUSUAL = _PACKED | _ELSEWHERE
|
|
43
|
+
_INVALID_FILE_SIZE = 0xFFFFFFFF
|
|
44
|
+
_VOLUME_PATH_LENGTH = 1024
|
|
45
|
+
_SHARED = 1024 # sizes of up to this many units (clusters or blocks) share their int object
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def allocation_for(root: str) -> Allocation:
|
|
49
|
+
"""How to tell the space taken on disk by the files beneath ``root`` (a folder on one volume)."""
|
|
50
|
+
if sys.platform == "win32":
|
|
51
|
+
return windows_allocation(cluster_size(root))
|
|
52
|
+
if hasattr(os.stat_result, "st_blocks"):
|
|
53
|
+
return blocks_allocation()
|
|
54
|
+
return _plain_size
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def blocks_allocation() -> Allocation:
|
|
58
|
+
"""The POSIX rule: ``st_blocks`` 512-byte units."""
|
|
59
|
+
shared = _multiples(512)
|
|
60
|
+
|
|
61
|
+
def allocated(_entry: os.DirEntry[str], info: os.stat_result) -> int:
|
|
62
|
+
blocks = info.st_blocks
|
|
63
|
+
return shared[blocks] if blocks < _SHARED else blocks * 512
|
|
64
|
+
|
|
65
|
+
return allocated
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def windows_allocation(cluster: int) -> Allocation:
|
|
69
|
+
"""The Windows rule (see the module docstring) for a volume with ``cluster``-byte clusters."""
|
|
70
|
+
|
|
71
|
+
shared = _multiples(cluster)
|
|
72
|
+
|
|
73
|
+
def rounded(size: int) -> int:
|
|
74
|
+
clusters = -(-size // cluster)
|
|
75
|
+
return shared[clusters] if clusters < _SHARED else clusters * cluster
|
|
76
|
+
|
|
77
|
+
def allocated(entry: os.DirEntry[str], info: os.stat_result) -> int:
|
|
78
|
+
attributes = info.st_file_attributes
|
|
79
|
+
if not attributes & _UNUSUAL: # nearly every file: no call to the system
|
|
80
|
+
return rounded(info.st_size)
|
|
81
|
+
if attributes & _ELSEWHERE:
|
|
82
|
+
return 0
|
|
83
|
+
exact = compressed_size(entry.path)
|
|
84
|
+
return rounded(info.st_size) if exact is None else exact
|
|
85
|
+
|
|
86
|
+
return allocated
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cluster_size(path: str) -> int:
|
|
90
|
+
"""Bytes per cluster of the volume holding ``path`` (Windows; ``DEFAULT_CLUSTER`` when it cannot be told)."""
|
|
91
|
+
kernel32 = _kernel32()
|
|
92
|
+
volume = ctypes.create_unicode_buffer(_VOLUME_PATH_LENGTH)
|
|
93
|
+
if not kernel32.GetVolumePathNameW(os.path.abspath(path), volume, _VOLUME_PATH_LENGTH):
|
|
94
|
+
return DEFAULT_CLUSTER
|
|
95
|
+
sectors, sector_bytes, free, total = (ctypes.c_ulong() for _ in range(4))
|
|
96
|
+
if not kernel32.GetDiskFreeSpaceW(volume.value, ctypes.byref(sectors), ctypes.byref(sector_bytes),
|
|
97
|
+
ctypes.byref(free), ctypes.byref(total)):
|
|
98
|
+
return DEFAULT_CLUSTER
|
|
99
|
+
return sectors.value * sector_bytes.value or DEFAULT_CLUSTER
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def compressed_size(path: str) -> int | None:
|
|
103
|
+
"""The bytes a compressed or sparse file really takes (Windows); None when the system cannot say."""
|
|
104
|
+
high = ctypes.c_ulong()
|
|
105
|
+
low = _kernel32().GetCompressedFileSizeW(path, ctypes.byref(high))
|
|
106
|
+
if low == _INVALID_FILE_SIZE and ctypes.get_last_error():
|
|
107
|
+
return None
|
|
108
|
+
return (high.value << 32) | low
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@functools.cache
|
|
112
|
+
def _kernel32() -> Any:
|
|
113
|
+
"""kernel32 with the signatures used here (its own instance, so other modules' settings never clash)."""
|
|
114
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore[attr-defined]
|
|
115
|
+
kernel32.GetVolumePathNameW.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_ulong]
|
|
116
|
+
kernel32.GetVolumePathNameW.restype = ctypes.c_int
|
|
117
|
+
kernel32.GetDiskFreeSpaceW.argtypes = [ctypes.c_wchar_p] + [ctypes.POINTER(ctypes.c_ulong)] * 4
|
|
118
|
+
kernel32.GetDiskFreeSpaceW.restype = ctypes.c_int
|
|
119
|
+
kernel32.GetCompressedFileSizeW.argtypes = [ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_ulong)]
|
|
120
|
+
kernel32.GetCompressedFileSizeW.restype = ctypes.c_ulong
|
|
121
|
+
return kernel32
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _multiples(unit: int) -> tuple[int, ...]:
|
|
125
|
+
return tuple(range(0, _SHARED * unit, unit))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _plain_size(_entry: os.DirEntry[str], info: os.stat_result) -> int:
|
|
129
|
+
return info.st_size
|