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.
Files changed (68) hide show
  1. je_file_tree-0.1.1/LICENSE +21 -0
  2. je_file_tree-0.1.1/PKG-INFO +177 -0
  3. je_file_tree-0.1.1/README.md +146 -0
  4. je_file_tree-0.1.1/je_file_tree/__init__.py +7 -0
  5. je_file_tree-0.1.1/je_file_tree/__main__.py +6 -0
  6. je_file_tree-0.1.1/je_file_tree/core/__init__.py +1 -0
  7. je_file_tree-0.1.1/je_file_tree/core/allocation.py +129 -0
  8. je_file_tree-0.1.1/je_file_tree/core/analysis.py +237 -0
  9. je_file_tree-0.1.1/je_file_tree/core/compare.py +131 -0
  10. je_file_tree-0.1.1/je_file_tree/core/duplicates.py +186 -0
  11. je_file_tree-0.1.1/je_file_tree/core/export.py +118 -0
  12. je_file_tree-0.1.1/je_file_tree/core/formatting.py +61 -0
  13. je_file_tree-0.1.1/je_file_tree/core/node.py +150 -0
  14. je_file_tree-0.1.1/je_file_tree/core/scanner.py +356 -0
  15. je_file_tree-0.1.1/je_file_tree/core/search.py +69 -0
  16. je_file_tree-0.1.1/je_file_tree/core/sunburst.py +58 -0
  17. je_file_tree-0.1.1/je_file_tree/core/treemap.py +179 -0
  18. je_file_tree-0.1.1/je_file_tree/gui/__init__.py +1 -0
  19. je_file_tree-0.1.1/je_file_tree/gui/app.py +60 -0
  20. je_file_tree-0.1.1/je_file_tree/gui/bar_chart.py +218 -0
  21. je_file_tree-0.1.1/je_file_tree/gui/changes_panel.py +148 -0
  22. je_file_tree-0.1.1/je_file_tree/gui/charts.py +102 -0
  23. je_file_tree-0.1.1/je_file_tree/gui/delegates.py +40 -0
  24. je_file_tree-0.1.1/je_file_tree/gui/duplicates_panel.py +282 -0
  25. je_file_tree-0.1.1/je_file_tree/gui/elevation.py +84 -0
  26. je_file_tree-0.1.1/je_file_tree/gui/file_actions.py +78 -0
  27. je_file_tree-0.1.1/je_file_tree/gui/help_dialog.py +25 -0
  28. je_file_tree-0.1.1/je_file_tree/gui/i18n.py +57 -0
  29. je_file_tree-0.1.1/je_file_tree/gui/icon.py +86 -0
  30. je_file_tree-0.1.1/je_file_tree/gui/main_window.py +604 -0
  31. je_file_tree-0.1.1/je_file_tree/gui/qt_translation.py +27 -0
  32. je_file_tree-0.1.1/je_file_tree/gui/reasons.py +15 -0
  33. je_file_tree-0.1.1/je_file_tree/gui/results_view.py +790 -0
  34. je_file_tree-0.1.1/je_file_tree/gui/scan_bar.py +95 -0
  35. je_file_tree-0.1.1/je_file_tree/gui/scan_worker.py +213 -0
  36. je_file_tree-0.1.1/je_file_tree/gui/search_panel.py +123 -0
  37. je_file_tree-0.1.1/je_file_tree/gui/strings.py +839 -0
  38. je_file_tree-0.1.1/je_file_tree/gui/sunburst_widget.py +271 -0
  39. je_file_tree-0.1.1/je_file_tree/gui/tables.py +259 -0
  40. je_file_tree-0.1.1/je_file_tree/gui/tree_model.py +349 -0
  41. je_file_tree-0.1.1/je_file_tree/gui/treemap_widget.py +288 -0
  42. je_file_tree-0.1.1/je_file_tree/gui/welcome.py +149 -0
  43. je_file_tree-0.1.1/je_file_tree.egg-info/PKG-INFO +177 -0
  44. je_file_tree-0.1.1/je_file_tree.egg-info/SOURCES.txt +67 -0
  45. je_file_tree-0.1.1/je_file_tree.egg-info/dependency_links.txt +1 -0
  46. je_file_tree-0.1.1/je_file_tree.egg-info/entry_points.txt +2 -0
  47. je_file_tree-0.1.1/je_file_tree.egg-info/requires.txt +1 -0
  48. je_file_tree-0.1.1/je_file_tree.egg-info/top_level.txt +1 -0
  49. je_file_tree-0.1.1/pyproject.toml +73 -0
  50. je_file_tree-0.1.1/setup.cfg +4 -0
  51. je_file_tree-0.1.1/test/test_allocation.py +81 -0
  52. je_file_tree-0.1.1/test/test_build_nuitka.py +61 -0
  53. je_file_tree-0.1.1/test/test_compare.py +85 -0
  54. je_file_tree-0.1.1/test/test_core.py +276 -0
  55. je_file_tree-0.1.1/test/test_duplicates.py +88 -0
  56. je_file_tree-0.1.1/test/test_elevation.py +142 -0
  57. je_file_tree-0.1.1/test/test_gui.py +690 -0
  58. je_file_tree-0.1.1/test/test_i18n.py +99 -0
  59. je_file_tree-0.1.1/test/test_icon.py +76 -0
  60. je_file_tree-0.1.1/test/test_layers.py +28 -0
  61. je_file_tree-0.1.1/test/test_readme_parity.py +72 -0
  62. je_file_tree-0.1.1/test/test_release.py +90 -0
  63. je_file_tree-0.1.1/test/test_scanner.py +194 -0
  64. je_file_tree-0.1.1/test/test_search.py +50 -0
  65. je_file_tree-0.1.1/test/test_start_script.py +19 -0
  66. je_file_tree-0.1.1/test/test_sunburst.py +45 -0
  67. je_file_tree-0.1.1/test/test_treemap.py +96 -0
  68. 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
+ ![FileTree showing a home folder: the folder tree on the left, the treemap on the right](docs/images/main_window_en.png)
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
+ ![FileTree showing a home folder: the folder tree on the left, the treemap on the right](docs/images/main_window_en.png)
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,7 @@
1
+ """FileTree: see where your disk space goes.
2
+
3
+ The package has two layers: ``je_file_tree.core`` scans a folder and computes the
4
+ numbers (no Qt dependency), and ``je_file_tree.gui`` shows them in a PySide6 window.
5
+ """
6
+
7
+ __version__ = "0.1.1"
@@ -0,0 +1,6 @@
1
+ """``python -m je_file_tree [folder]`` opens the window (and starts scanning ``folder`` if given)."""
2
+
3
+ from je_file_tree.gui.app import run
4
+
5
+ if __name__ == "__main__":
6
+ run()
@@ -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