@bobfrankston/tswalk 1.0.1
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.
- package/.claude/settings.local.json +9 -0
- package/.editorconfig +9 -0
- package/.gitattributes +2 -0
- package/.vscode/launch.json +32 -0
- package/.vscode/settings.json +22 -0
- package/.vscode/tasks.json +23 -0
- package/bin/tswalk.js +0 -0
- package/diskwalker.js +240 -0
- package/diskwalker.js.map +1 -0
- package/diskwalker.ts +271 -0
- package/diskwalker2.js +280 -0
- package/diskwalker2.js.map +1 -0
- package/diskwalker2.ts +319 -0
- package/index.js +143 -0
- package/index.js.map +1 -0
- package/index.ts +149 -0
- package/package.json +26 -0
- package/tsconfig.json +14 -0
package/diskwalker2.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { styleText } from 'node:util';
|
|
4
|
+
import { listDirectory, getDriveInfo } from '@bobfrankston/dirutil';
|
|
5
|
+
// Error types to suppress unless verbose
|
|
6
|
+
const suppressedErrors = ['ENOENT', 'EPERM', 'EACCES'];
|
|
7
|
+
// Directories to suppress errors from
|
|
8
|
+
const suppressedDirectories = ['$RECYCLE.BIN', 'System Volume Information'];
|
|
9
|
+
export class DiskWalker {
|
|
10
|
+
topPath;
|
|
11
|
+
static defaultOptions = {
|
|
12
|
+
depth: 1,
|
|
13
|
+
threshold: 0,
|
|
14
|
+
cmd: '',
|
|
15
|
+
verbose: false,
|
|
16
|
+
showCompressed: false,
|
|
17
|
+
finding: null,
|
|
18
|
+
forceAcl: false,
|
|
19
|
+
quiet: false,
|
|
20
|
+
sizeUnit: 'MB'
|
|
21
|
+
};
|
|
22
|
+
options;
|
|
23
|
+
walker;
|
|
24
|
+
lastProgressTime = 0;
|
|
25
|
+
static blockSizeCache = new Map();
|
|
26
|
+
constructor(topPath = '.', options = {}) {
|
|
27
|
+
this.topPath = topPath;
|
|
28
|
+
this.options = { ...DiskWalker.defaultOptions, ...options };
|
|
29
|
+
this.topPath = path.resolve(topPath);
|
|
30
|
+
this.walker = new WalkTree(this.topPath, this.options, this.showProgress.bind(this));
|
|
31
|
+
}
|
|
32
|
+
truncatePath(fullPath, maxLength = 64) {
|
|
33
|
+
if (fullPath.length <= maxLength)
|
|
34
|
+
return fullPath;
|
|
35
|
+
const half = Math.floor((maxLength - 3) / 2);
|
|
36
|
+
const start = fullPath.slice(0, half);
|
|
37
|
+
const end = fullPath.slice(-half);
|
|
38
|
+
return `${start}...${end}`;
|
|
39
|
+
}
|
|
40
|
+
showProgress(walker, currentPath) {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
if (now - this.lastProgressTime > 1000 && !this.options.quiet) {
|
|
43
|
+
const truncatedPath = this.truncatePath(currentPath);
|
|
44
|
+
process.stdout.write('\x1b[2K\r'); // Clear entire line and return to start
|
|
45
|
+
const line = styleText(['blue'], `${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ${truncatedPath}`);
|
|
46
|
+
process.stdout.write(line);
|
|
47
|
+
this.lastProgressTime = now;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async walk() {
|
|
51
|
+
try {
|
|
52
|
+
await this.walker.walk();
|
|
53
|
+
if (!this.options.quiet) {
|
|
54
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
55
|
+
}
|
|
56
|
+
const pather = `${styleText(['blue'], `${path.dirname(this.topPath)}${path.sep}`)}${styleText(['bgBlack', 'bold', 'yellow'], path.basename(this.topPath))}`;
|
|
57
|
+
console.log(`${this.walker.formatSize()} ${this.options.sizeUnit} ${pather}`);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.error('Failed:', err.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class WalkTree {
|
|
65
|
+
dirPath;
|
|
66
|
+
options;
|
|
67
|
+
progressCallback;
|
|
68
|
+
atDepth;
|
|
69
|
+
length = 0;
|
|
70
|
+
compressedLength = 0;
|
|
71
|
+
fileCount = 0;
|
|
72
|
+
dirCount = 0;
|
|
73
|
+
totalFileCount = 0;
|
|
74
|
+
totalDirCount = 0;
|
|
75
|
+
otherCount = 0;
|
|
76
|
+
prefix;
|
|
77
|
+
nextTitleUpdate = new Date(0);
|
|
78
|
+
seenErrors = new Set();
|
|
79
|
+
blockSize = 4096; // default 4KB, will be updated
|
|
80
|
+
constructor(dirPath, options, progressCallback, atDepth = 0) {
|
|
81
|
+
this.dirPath = dirPath;
|
|
82
|
+
this.options = options;
|
|
83
|
+
this.progressCallback = progressCallback;
|
|
84
|
+
this.atDepth = atDepth;
|
|
85
|
+
this.prefix = ' '.repeat(5 + atDepth * 5);
|
|
86
|
+
if (options.verbose) {
|
|
87
|
+
this.prefix = atDepth.toString().padStart(2, '0') + ' ' + this.prefix;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
getDiskBlockSize(drivePath) {
|
|
91
|
+
const drive = path.parse(drivePath).root;
|
|
92
|
+
if (DiskWalker.blockSizeCache.has(drive)) {
|
|
93
|
+
return DiskWalker.blockSizeCache.get(drive);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const driveInfo = getDriveInfo(drivePath);
|
|
97
|
+
const blockSize = driveInfo?.blockSize || 4096;
|
|
98
|
+
DiskWalker.blockSizeCache.set(drive, blockSize);
|
|
99
|
+
return blockSize;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
// Fallback to default 4KB block size
|
|
103
|
+
DiskWalker.blockSizeCache.set(drive, 4096);
|
|
104
|
+
return 4096;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
roundUpToBlockSize(fileSize, blockSize) {
|
|
108
|
+
return Math.ceil(fileSize / blockSize) * blockSize;
|
|
109
|
+
}
|
|
110
|
+
formatSize() {
|
|
111
|
+
const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
|
|
112
|
+
const size = this.length / divisor;
|
|
113
|
+
const formatted = size.toLocaleString(undefined, { minimumFractionDigits: 2 });
|
|
114
|
+
if (this.options.showCompressed && this.length !== this.compressedLength) {
|
|
115
|
+
const compressedSize = this.compressedLength / divisor;
|
|
116
|
+
return `${formatted}/${compressedSize.toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
|
|
117
|
+
}
|
|
118
|
+
return formatted;
|
|
119
|
+
}
|
|
120
|
+
logError(context, err) {
|
|
121
|
+
// Only suppress errors if explicitly quiet, or if it's a known unimportant error
|
|
122
|
+
if (this.options.quiet ||
|
|
123
|
+
(!this.options.verbose &&
|
|
124
|
+
(suppressedErrors.some(e => err.message.includes(e)) ||
|
|
125
|
+
suppressedDirectories.some(d => this.dirPath.includes(d))))) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const errorKey = `${context}:${err.message}:${this.dirPath}`;
|
|
129
|
+
if (!this.seenErrors.has(errorKey)) {
|
|
130
|
+
console.error(styleText(['red'], `Error processing ${context}: ${err.message}`));
|
|
131
|
+
this.seenErrors.add(errorKey);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
processFilesFromEntries(entries) {
|
|
135
|
+
try {
|
|
136
|
+
if (Date.now() > this.nextTitleUpdate.getTime()) {
|
|
137
|
+
process.title = this.dirPath;
|
|
138
|
+
this.nextTitleUpdate = new Date(Date.now() + 500);
|
|
139
|
+
}
|
|
140
|
+
// Get block size once for this directory's drive
|
|
141
|
+
if (this.blockSize === 4096) { // Only query if we haven't set it yet
|
|
142
|
+
this.blockSize = this.getDiskBlockSize(this.dirPath);
|
|
143
|
+
}
|
|
144
|
+
const fileEntries = entries.filter(entry => entry.isFile);
|
|
145
|
+
for (const entry of fileEntries) {
|
|
146
|
+
this.fileCount++;
|
|
147
|
+
this.totalFileCount++;
|
|
148
|
+
const fileSize = entry.size || 0;
|
|
149
|
+
const blockAlignedSize = this.roundUpToBlockSize(fileSize, this.blockSize);
|
|
150
|
+
this.length += blockAlignedSize;
|
|
151
|
+
this.compressedLength += fileSize; // Keep original size for compressed display
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.logError('directory', err);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async executeCommand() {
|
|
159
|
+
if (!this.options.cmd)
|
|
160
|
+
return;
|
|
161
|
+
const currentDir = process.cwd();
|
|
162
|
+
try {
|
|
163
|
+
process.chdir(this.dirPath);
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const proc = spawn(this.options.cmd, [], { shell: true });
|
|
166
|
+
proc.on('close', resolve);
|
|
167
|
+
proc.on('error', reject);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
this.logError('command execution', err);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
process.chdir(currentDir);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async findFilesFromEntries(entries) {
|
|
178
|
+
if (!this.options.finding)
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
for (const pattern of this.options.finding) {
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (entry.name.match(pattern)) {
|
|
184
|
+
const size = (entry.size || 0) / 1024;
|
|
185
|
+
const mtime = new Date(entry.mtime);
|
|
186
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
187
|
+
console.log(`${mtime.toISOString().slice(0, 19).replace('T', ' ')} ` +
|
|
188
|
+
`${size.toFixed(1).padStart(8)}KB ${this.prefix} ${path.join(this.dirPath, entry.name)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
this.logError('file search', err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async walk() {
|
|
198
|
+
try {
|
|
199
|
+
this.progressCallback(this, this.dirPath);
|
|
200
|
+
// try {
|
|
201
|
+
// // console.log(`DEBUG: Accessing directory: ${this.dirPath}`);
|
|
202
|
+
// // fs.accessSync(this.dirPath);
|
|
203
|
+
// // console.log(`DEBUG: Accessed directory: ${this.dirPath}`);
|
|
204
|
+
// fs.readdirSync(this.dirPath); // Ensure we can read the directory
|
|
205
|
+
// // console.log(`DEBUG: Read directory: ${this.dirPath}`);
|
|
206
|
+
// }
|
|
207
|
+
// catch (e: any) {
|
|
208
|
+
// // console.error(`DEBUG: Failed to access or read directory ${this.dirPath}: ${e.message}`);
|
|
209
|
+
// debugger;
|
|
210
|
+
// throw e
|
|
211
|
+
// }
|
|
212
|
+
// Get directory entries once
|
|
213
|
+
// console.log(`DEBUG: Walking directory: ${this.dirPath}`);
|
|
214
|
+
const entries = listDirectory(this.dirPath, { stats: true });
|
|
215
|
+
// console.log(`DEBUG: Walked directory: ${this.dirPath}`);
|
|
216
|
+
if (this.options.verbose) {
|
|
217
|
+
console.log(`DEBUG: Found ${entries.length} entries in ${this.dirPath}`);
|
|
218
|
+
entries.forEach(e => console.log(` ${e.name}: isDir=${e.isDirectory}, isFile=${e.isFile}`));
|
|
219
|
+
}
|
|
220
|
+
if (this.options.cmd || this.options.finding || this.options.forceAcl) {
|
|
221
|
+
if (this.options.forceAcl) {
|
|
222
|
+
// ACL operations not implemented
|
|
223
|
+
}
|
|
224
|
+
if (this.options.cmd) {
|
|
225
|
+
await this.executeCommand();
|
|
226
|
+
}
|
|
227
|
+
if (this.options.finding) {
|
|
228
|
+
// Use the entries we already have for finding
|
|
229
|
+
await this.findFilesFromEntries(entries);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Process files from the entries we already have
|
|
234
|
+
this.processFilesFromEntries(entries);
|
|
235
|
+
}
|
|
236
|
+
// Process directories for recursion (skip symlinks by default)
|
|
237
|
+
const directoryEntries = entries.filter(entry => entry.isDirectory && !entry.isSymlink);
|
|
238
|
+
for (const entry of directoryEntries) {
|
|
239
|
+
try {
|
|
240
|
+
const subPath = path.join(this.dirPath, entry.name);
|
|
241
|
+
const walker = new WalkTree(subPath, this.options, this.progressCallback, this.atDepth + 1);
|
|
242
|
+
await walker.walk();
|
|
243
|
+
if (this.options.verbose) {
|
|
244
|
+
console.log(`DEBUG: Completed walking ${entry.name}, about to process results`);
|
|
245
|
+
}
|
|
246
|
+
this.dirCount++;
|
|
247
|
+
this.length += walker.length;
|
|
248
|
+
this.compressedLength += walker.compressedLength;
|
|
249
|
+
this.totalFileCount += walker.totalFileCount;
|
|
250
|
+
this.totalDirCount += walker.totalDirCount + 1;
|
|
251
|
+
const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
|
|
252
|
+
const size = walker.length / divisor;
|
|
253
|
+
if (this.options.verbose) {
|
|
254
|
+
console.log(`DEBUG: ${entry.name}: size=${size}, threshold=${this.options.threshold}, depth=${this.atDepth}/${this.options.depth}, length=${walker.length}`);
|
|
255
|
+
console.log(`DEBUG: condition check: (${this.options.threshold} === 0 || ${size} >= ${this.options.threshold}) && ${this.atDepth} < ${this.options.depth} = ${(this.options.threshold === 0 || size >= this.options.threshold) && this.atDepth < this.options.depth}`);
|
|
256
|
+
}
|
|
257
|
+
if ((this.options.threshold === 0 || size >= this.options.threshold) && this.atDepth < this.options.depth) {
|
|
258
|
+
if (!this.options.quiet) {
|
|
259
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
260
|
+
}
|
|
261
|
+
console.log(`${this.prefix} ${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ` +
|
|
262
|
+
`${walker.totalDirCount.toString().padStart(5)} ` +
|
|
263
|
+
`${walker.totalFileCount.toString().padStart(6)} ${entry.name}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
if (this.options.verbose) {
|
|
268
|
+
console.log(`DEBUG: Error processing ${entry.name}: ${err}`);
|
|
269
|
+
}
|
|
270
|
+
this.logError(entry.name, err);
|
|
271
|
+
// Continue with next directory instead of aborting
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
this.logError('directory walk', err);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=diskwalker2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diskwalker2.js","sourceRoot":"","sources":["diskwalker2.ts"],"names":[],"mappings":"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"}
|
package/diskwalker2.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { styleText } from 'node:util';
|
|
5
|
+
import { listDirectory, getDriveInfo, type DirectoryEntry } from '@bobfrankston/dirutil';
|
|
6
|
+
|
|
7
|
+
interface WalkOptions {
|
|
8
|
+
depth: number;
|
|
9
|
+
threshold: number;
|
|
10
|
+
cmd: string;
|
|
11
|
+
verbose: boolean;
|
|
12
|
+
showCompressed: boolean;
|
|
13
|
+
finding: string[] | null;
|
|
14
|
+
forceAcl: boolean;
|
|
15
|
+
quiet: boolean;
|
|
16
|
+
sizeUnit: 'MB' | 'GB';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ProgressCallback = (walker: WalkTree, currentPath: string) => void;
|
|
20
|
+
|
|
21
|
+
// Error types to suppress unless verbose
|
|
22
|
+
const suppressedErrors = ['ENOENT', 'EPERM', 'EACCES'];
|
|
23
|
+
|
|
24
|
+
// Directories to suppress errors from
|
|
25
|
+
const suppressedDirectories = ['$RECYCLE.BIN', 'System Volume Information'];
|
|
26
|
+
|
|
27
|
+
export class DiskWalker {
|
|
28
|
+
private static defaultOptions: WalkOptions = {
|
|
29
|
+
depth: 1,
|
|
30
|
+
threshold: 0,
|
|
31
|
+
cmd: '',
|
|
32
|
+
verbose: false,
|
|
33
|
+
showCompressed: false,
|
|
34
|
+
finding: null,
|
|
35
|
+
forceAcl: false,
|
|
36
|
+
quiet: false,
|
|
37
|
+
sizeUnit: 'MB'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
private options: WalkOptions;
|
|
41
|
+
private walker: WalkTree;
|
|
42
|
+
private lastProgressTime: number = 0;
|
|
43
|
+
public static blockSizeCache: Map<string, number> = new Map();
|
|
44
|
+
|
|
45
|
+
constructor(private topPath: string = '.', options: Partial<WalkOptions> = {}) {
|
|
46
|
+
this.options = { ...DiskWalker.defaultOptions, ...options };
|
|
47
|
+
this.topPath = path.resolve(topPath);
|
|
48
|
+
this.walker = new WalkTree(this.topPath, this.options, this.showProgress.bind(this));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private truncatePath(fullPath: string, maxLength: number = 64): string {
|
|
52
|
+
if (fullPath.length <= maxLength) return fullPath;
|
|
53
|
+
const half = Math.floor((maxLength - 3) / 2);
|
|
54
|
+
const start = fullPath.slice(0, half);
|
|
55
|
+
const end = fullPath.slice(-half);
|
|
56
|
+
return `${start}...${end}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private showProgress(walker: WalkTree, currentPath: string): void {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (now - this.lastProgressTime > 1000 && !this.options.quiet) {
|
|
62
|
+
const truncatedPath = this.truncatePath(currentPath);
|
|
63
|
+
process.stdout.write('\x1b[2K\r'); // Clear entire line and return to start
|
|
64
|
+
const line = styleText(['blue'], `${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ${truncatedPath}`);
|
|
65
|
+
process.stdout.write(line);
|
|
66
|
+
|
|
67
|
+
this.lastProgressTime = now;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async walk(): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
await this.walker.walk();
|
|
74
|
+
if (!this.options.quiet) {
|
|
75
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
76
|
+
}
|
|
77
|
+
const pather = `${styleText(['blue'], `${path.dirname(this.topPath)}${path.sep}`)}${styleText(['bgBlack', 'bold', 'yellow'], path.basename(this.topPath))}`
|
|
78
|
+
console.log(`${this.walker.formatSize()} ${this.options.sizeUnit} ${pather}`);
|
|
79
|
+
} catch (err: any) {
|
|
80
|
+
console.error('Failed:', (err as Error).message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class WalkTree {
|
|
86
|
+
private length: number = 0;
|
|
87
|
+
private compressedLength: number = 0;
|
|
88
|
+
private fileCount: number = 0;
|
|
89
|
+
private dirCount: number = 0;
|
|
90
|
+
private totalFileCount: number = 0;
|
|
91
|
+
private totalDirCount: number = 0;
|
|
92
|
+
private otherCount: number = 0;
|
|
93
|
+
private prefix: string;
|
|
94
|
+
private nextTitleUpdate: Date = new Date(0);
|
|
95
|
+
private seenErrors = new Set<string>();
|
|
96
|
+
private blockSize: number = 4096; // default 4KB, will be updated
|
|
97
|
+
|
|
98
|
+
constructor(
|
|
99
|
+
private dirPath: string,
|
|
100
|
+
private options: WalkOptions,
|
|
101
|
+
private progressCallback: ProgressCallback,
|
|
102
|
+
private atDepth: number = 0
|
|
103
|
+
) {
|
|
104
|
+
this.prefix = ' '.repeat(5 + atDepth * 5);
|
|
105
|
+
if (options.verbose) {
|
|
106
|
+
this.prefix = atDepth.toString().padStart(2, '0') + ' ' + this.prefix;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private getDiskBlockSize(drivePath: string): number {
|
|
111
|
+
const drive = path.parse(drivePath).root;
|
|
112
|
+
|
|
113
|
+
if (DiskWalker.blockSizeCache.has(drive)) {
|
|
114
|
+
return DiskWalker.blockSizeCache.get(drive)!;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const driveInfo = getDriveInfo(drivePath);
|
|
119
|
+
const blockSize = driveInfo?.blockSize || 4096;
|
|
120
|
+
DiskWalker.blockSizeCache.set(drive, blockSize);
|
|
121
|
+
return blockSize;
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
// Fallback to default 4KB block size
|
|
124
|
+
DiskWalker.blockSizeCache.set(drive, 4096);
|
|
125
|
+
return 4096;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private roundUpToBlockSize(fileSize: number, blockSize: number): number {
|
|
130
|
+
return Math.ceil(fileSize / blockSize) * blockSize;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public formatSize(): string {
|
|
134
|
+
const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
|
|
135
|
+
const size = this.length / divisor;
|
|
136
|
+
const formatted = size.toLocaleString(undefined, { minimumFractionDigits: 2 });
|
|
137
|
+
|
|
138
|
+
if (this.options.showCompressed && this.length !== this.compressedLength) {
|
|
139
|
+
const compressedSize = this.compressedLength / divisor;
|
|
140
|
+
return `${formatted}/${compressedSize.toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return formatted;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private logError(context: string, err: Error): void {
|
|
147
|
+
// Only suppress errors if explicitly quiet, or if it's a known unimportant error
|
|
148
|
+
if (this.options.quiet ||
|
|
149
|
+
(!this.options.verbose &&
|
|
150
|
+
(suppressedErrors.some(e => err.message.includes(e)) ||
|
|
151
|
+
suppressedDirectories.some(d => this.dirPath.includes(d))))) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const errorKey = `${context}:${err.message}:${this.dirPath}`;
|
|
156
|
+
if (!this.seenErrors.has(errorKey)) {
|
|
157
|
+
console.error(styleText(['red'], `Error processing ${context}: ${err.message}`));
|
|
158
|
+
this.seenErrors.add(errorKey);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private processFilesFromEntries(entries: DirectoryEntry[]): void {
|
|
163
|
+
try {
|
|
164
|
+
if (Date.now() > this.nextTitleUpdate.getTime()) {
|
|
165
|
+
process.title = this.dirPath;
|
|
166
|
+
this.nextTitleUpdate = new Date(Date.now() + 500);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get block size once for this directory's drive
|
|
170
|
+
if (this.blockSize === 4096) { // Only query if we haven't set it yet
|
|
171
|
+
this.blockSize = this.getDiskBlockSize(this.dirPath);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fileEntries = entries.filter(entry => entry.isFile);
|
|
175
|
+
for (const entry of fileEntries) {
|
|
176
|
+
this.fileCount++;
|
|
177
|
+
this.totalFileCount++;
|
|
178
|
+
const fileSize = entry.size || 0;
|
|
179
|
+
const blockAlignedSize = this.roundUpToBlockSize(fileSize, this.blockSize);
|
|
180
|
+
this.length += blockAlignedSize;
|
|
181
|
+
this.compressedLength += fileSize; // Keep original size for compressed display
|
|
182
|
+
}
|
|
183
|
+
} catch (err: any) {
|
|
184
|
+
this.logError('directory', err as Error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async executeCommand(): Promise<void> {
|
|
189
|
+
if (!this.options.cmd) return;
|
|
190
|
+
|
|
191
|
+
const currentDir = process.cwd();
|
|
192
|
+
try {
|
|
193
|
+
process.chdir(this.dirPath);
|
|
194
|
+
await new Promise((resolve, reject) => {
|
|
195
|
+
const proc = spawn(this.options.cmd, [], { shell: true });
|
|
196
|
+
proc.on('close', resolve);
|
|
197
|
+
proc.on('error', reject);
|
|
198
|
+
});
|
|
199
|
+
} catch (err: any) {
|
|
200
|
+
this.logError('command execution', err as Error);
|
|
201
|
+
} finally {
|
|
202
|
+
process.chdir(currentDir);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async findFilesFromEntries(entries: DirectoryEntry[]): Promise<void> {
|
|
207
|
+
if (!this.options.finding) return;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
for (const pattern of this.options.finding) {
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
if (entry.name.match(pattern)) {
|
|
213
|
+
const size = (entry.size || 0) / 1024;
|
|
214
|
+
const mtime = new Date(entry.mtime);
|
|
215
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
216
|
+
console.log(
|
|
217
|
+
`${mtime.toISOString().slice(0, 19).replace('T', ' ')} ` +
|
|
218
|
+
`${size.toFixed(1).padStart(8)}KB ${this.prefix} ${path.join(this.dirPath, entry.name)}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch (err: any) {
|
|
224
|
+
this.logError('file search', err as Error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public async walk(): Promise<void> {
|
|
229
|
+
try {
|
|
230
|
+
this.progressCallback(this, this.dirPath);
|
|
231
|
+
|
|
232
|
+
// try {
|
|
233
|
+
// // console.log(`DEBUG: Accessing directory: ${this.dirPath}`);
|
|
234
|
+
// // fs.accessSync(this.dirPath);
|
|
235
|
+
// // console.log(`DEBUG: Accessed directory: ${this.dirPath}`);
|
|
236
|
+
// fs.readdirSync(this.dirPath); // Ensure we can read the directory
|
|
237
|
+
// // console.log(`DEBUG: Read directory: ${this.dirPath}`);
|
|
238
|
+
// }
|
|
239
|
+
// catch (e: any) {
|
|
240
|
+
// // console.error(`DEBUG: Failed to access or read directory ${this.dirPath}: ${e.message}`);
|
|
241
|
+
// debugger;
|
|
242
|
+
// throw e
|
|
243
|
+
// }
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// Get directory entries once
|
|
247
|
+
// console.log(`DEBUG: Walking directory: ${this.dirPath}`);
|
|
248
|
+
const entries = listDirectory(this.dirPath, { stats: true });
|
|
249
|
+
// console.log(`DEBUG: Walked directory: ${this.dirPath}`);
|
|
250
|
+
|
|
251
|
+
if (this.options.verbose) {
|
|
252
|
+
console.log(`DEBUG: Found ${entries.length} entries in ${this.dirPath}`);
|
|
253
|
+
entries.forEach(e => console.log(` ${e.name}: isDir=${e.isDirectory}, isFile=${e.isFile}`));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.options.cmd || this.options.finding || this.options.forceAcl) {
|
|
257
|
+
if (this.options.forceAcl) {
|
|
258
|
+
// ACL operations not implemented
|
|
259
|
+
}
|
|
260
|
+
if (this.options.cmd) {
|
|
261
|
+
await this.executeCommand();
|
|
262
|
+
}
|
|
263
|
+
if (this.options.finding) {
|
|
264
|
+
// Use the entries we already have for finding
|
|
265
|
+
await this.findFilesFromEntries(entries);
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
// Process files from the entries we already have
|
|
269
|
+
this.processFilesFromEntries(entries);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Process directories for recursion (skip symlinks by default)
|
|
273
|
+
const directoryEntries = entries.filter(entry => entry.isDirectory && !entry.isSymlink);
|
|
274
|
+
for (const entry of directoryEntries) {
|
|
275
|
+
try {
|
|
276
|
+
const subPath = path.join(this.dirPath, entry.name);
|
|
277
|
+
const walker = new WalkTree(subPath, this.options, this.progressCallback, this.atDepth + 1);
|
|
278
|
+
|
|
279
|
+
await walker.walk();
|
|
280
|
+
if (this.options.verbose) {
|
|
281
|
+
console.log(`DEBUG: Completed walking ${entry.name}, about to process results`);
|
|
282
|
+
}
|
|
283
|
+
this.dirCount++;
|
|
284
|
+
this.length += walker.length;
|
|
285
|
+
this.compressedLength += walker.compressedLength;
|
|
286
|
+
this.totalFileCount += walker.totalFileCount;
|
|
287
|
+
this.totalDirCount += walker.totalDirCount + 1;
|
|
288
|
+
|
|
289
|
+
const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
|
|
290
|
+
const size = walker.length / divisor;
|
|
291
|
+
if (this.options.verbose) {
|
|
292
|
+
console.log(`DEBUG: ${entry.name}: size=${size}, threshold=${this.options.threshold}, depth=${this.atDepth}/${this.options.depth}, length=${walker.length}`);
|
|
293
|
+
console.log(`DEBUG: condition check: (${this.options.threshold} === 0 || ${size} >= ${this.options.threshold}) && ${this.atDepth} < ${this.options.depth} = ${(this.options.threshold === 0 || size >= this.options.threshold) && this.atDepth < this.options.depth}`);
|
|
294
|
+
}
|
|
295
|
+
if ((this.options.threshold === 0 || size >= this.options.threshold) && this.atDepth < this.options.depth) {
|
|
296
|
+
if (!this.options.quiet) {
|
|
297
|
+
process.stdout.write('\x1b[2K\r'); // Clear progress line
|
|
298
|
+
}
|
|
299
|
+
console.log(
|
|
300
|
+
`${this.prefix} ${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ` +
|
|
301
|
+
`${walker.totalDirCount.toString().padStart(5)} ` +
|
|
302
|
+
`${walker.totalFileCount.toString().padStart(6)} ${entry.name}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
} catch (err: any) {
|
|
306
|
+
if (this.options.verbose) {
|
|
307
|
+
console.log(`DEBUG: Error processing ${entry.name}: ${err}`);
|
|
308
|
+
}
|
|
309
|
+
this.logError(entry.name, err as Error);
|
|
310
|
+
// Continue with next directory instead of aborting
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (err: any) {
|
|
314
|
+
this.logError('directory walk', err as Error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { WalkOptions };
|