algorembrant commited on
Commit
68c75e6
·
verified ·
1 Parent(s): 03f7436

Upload file.py

Browse files
Files changed (1) hide show
  1. file.py +149 -0
file.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =============================================================================
2
+ # USAGE EXAMPLES (run from any directory)
3
+ # -----------------------------------------------------------------------------
4
+ # python file.py -scan C:\Users\User\Desktop\debugrem
5
+ # python file.py -scan "D:\My Data\Projects"
6
+ # python file.py --scan .
7
+ # python file.py -scan C:\Windows\Temp -j 32
8
+ #
9
+ # FLAGS
10
+ # -scan, --scan PATH Root folder to scan (required). Only direct children
11
+ # of PATH are listed; folder sizes are total bytes of
12
+ # all nested files (recursive).
13
+ # -j, --jobs N Parallel workers for sizing top-level folders
14
+ # (default: min(32, CPU count * 4)).
15
+ #
16
+ # OUTPUT FORMAT (columns separated by two spaces)
17
+ # <name> <path> <size_KB>
18
+ # Paths use OS separators; directories end with a separator.
19
+ # Rows are sorted by size_KB descending (largest first); ties by name.
20
+ # =============================================================================
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import os
26
+ import sys
27
+ from concurrent.futures import ThreadPoolExecutor, as_completed
28
+ from os import scandir, stat_result
29
+
30
+
31
+ def _bytes_to_kb(n: int) -> int:
32
+ return (n + 1023) // 1024 if n else 0
33
+
34
+
35
+ def _tree_size_bytes(root: str) -> int:
36
+ """Sum st_size of all regular files under root (iterative, no recursion limit)."""
37
+ total = 0
38
+ stack = [root]
39
+ push = stack.append
40
+ pop = stack.pop
41
+
42
+ while stack:
43
+ d = pop()
44
+ try:
45
+ with scandir(d) as it:
46
+ for ent in it:
47
+ try:
48
+ if ent.is_file(follow_symlinks=False):
49
+ st: stat_result = ent.stat(follow_symlinks=False)
50
+ total += st.st_size
51
+ elif ent.is_dir(follow_symlinks=False):
52
+ push(ent.path)
53
+ except OSError:
54
+ continue
55
+ except OSError:
56
+ continue
57
+ return total
58
+
59
+
60
+ def _format_line(name: str, path_for_display: str, size_kb: int) -> str:
61
+ return f"{name} {path_for_display} {size_kb}"
62
+
63
+
64
+ def scan_root(root: str, jobs: int) -> None:
65
+ root = os.path.abspath(os.path.normpath(root))
66
+ if not os.path.isdir(root):
67
+ print(f"Not a directory: {root}", file=sys.stderr)
68
+ sys.exit(2)
69
+
70
+ sep = os.sep
71
+ entries: list[tuple[str, str, bool]] = []
72
+ try:
73
+ with scandir(root) as it:
74
+ for ent in it:
75
+ try:
76
+ is_dir = ent.is_dir(follow_symlinks=False)
77
+ except OSError:
78
+ continue
79
+ entries.append((ent.name, ent.path, is_dir))
80
+ except OSError as e:
81
+ print(f"Cannot read directory {root}: {e}", file=sys.stderr)
82
+ sys.exit(1)
83
+
84
+ files_ready: list[tuple[str, str, int]] = []
85
+ dir_jobs: list[tuple[str, str]] = []
86
+
87
+ for name, fullpath, is_dir in entries:
88
+ if is_dir:
89
+ display = fullpath if fullpath.endswith(sep) else fullpath + sep
90
+ dir_jobs.append((name, fullpath))
91
+ else:
92
+ try:
93
+ st = os.stat(fullpath, follow_symlinks=False)
94
+ sz = st.st_size
95
+ except OSError:
96
+ sz = 0
97
+ display = fullpath
98
+ files_ready.append((name, display, _bytes_to_kb(sz)))
99
+
100
+ dirs_ready: list[tuple[str, str, int]] = []
101
+ if dir_jobs:
102
+ workers = max(1, min(jobs, len(dir_jobs)))
103
+ with ThreadPoolExecutor(max_workers=workers) as ex:
104
+ futs = {
105
+ ex.submit(_tree_size_bytes, p): (n, p)
106
+ for n, p in dir_jobs
107
+ }
108
+ for fut in as_completed(futs):
109
+ name, fullpath = futs[fut]
110
+ display = fullpath if fullpath.endswith(sep) else fullpath + sep
111
+ try:
112
+ b = fut.result()
113
+ except Exception:
114
+ b = 0
115
+ dirs_ready.append((name, display, _bytes_to_kb(b)))
116
+
117
+ out: list[tuple[str, str, int]] = [*files_ready, *dirs_ready]
118
+ out.sort(key=lambda row: (-row[2], row[0].lower()))
119
+
120
+ for name, path_disp, kb in out:
121
+ print(_format_line(name, path_disp, kb))
122
+
123
+
124
+ def main() -> None:
125
+ p = argparse.ArgumentParser(
126
+ description="List direct children of PATH with sizes (folders = recursive total).",
127
+ )
128
+ p.add_argument(
129
+ "-scan",
130
+ "--scan",
131
+ dest="root",
132
+ metavar="PATH",
133
+ required=True,
134
+ help="Root directory to scan",
135
+ )
136
+ p.add_argument(
137
+ "-j",
138
+ "--jobs",
139
+ type=int,
140
+ default=max(1, min(32, (os.cpu_count() or 4) * 4)),
141
+ metavar="N",
142
+ help="Thread workers for parallel folder sizing",
143
+ )
144
+ args = p.parse_args()
145
+ scan_root(args.root, args.jobs)
146
+
147
+
148
+ if __name__ == "__main__":
149
+ main()