blob: 4eadde7bf0a9ae05da2f4acd2a2fac1799889bcd (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
// Very simple, bare-bones file size loader which takes a bunch of file
// paths, gets their filesizes, and resolves a promise when it's done.
//
// Once the size of a path has been loaded, it's available synchronously -
// so this may be provided to code areas which don't support async code!
//
// This class also supports loading more paths after the initial batch is
// done (it uses a queue system) - but make sure you pause any sync code
// depending on the results until it's finished. waitUntilDoneLoading will
// always hold until the queue is completely emptied, including waiting for
// any entries to finish which were added after the wait function itself was
// called. (Same if you decide to await loadPaths. Sorry that function won't
// resolve as soon as just the paths it provided are finished - that's not
// really a worthwhile feature to support for its complexity here, since
// basically all this should process almost instantaneously anyway!)
//
// This only processes files one at a time because I'm lazy and stat calls
// are very, very fast.
import {stat} from 'node:fs/promises';
import {logWarn} from '#cli';
export default class FileSizePreloader {
#paths = [];
#sizes = [];
#loadedPathIndex = -1;
#loadingPromise = null;
#resolveLoadingPromise = null;
hadErrored = false;
loadPaths(...paths) {
this.#paths.push(...paths.filter((p) => !this.#paths.includes(p)));
return this.#startLoadingPaths();
}
waitUntilDoneLoading() {
return this.#loadingPromise ?? Promise.resolve();
}
#startLoadingPaths() {
if (this.#loadingPromise) {
return this.#loadingPromise;
}
this.#loadingPromise = new Promise((resolve) => {
this.#resolveLoadingPromise = resolve;
});
this.#loadNextPath();
return this.#loadingPromise;
}
async #loadNextPath() {
if (this.#loadedPathIndex === this.#paths.length - 1) {
return this.#doneLoadingPaths();
}
let size;
const path = this.#paths[this.#loadedPathIndex + 1];
try {
size = await this.readFileSize(path);
} catch (error) {
// Oops! Discard that path, and don't increment the index before
// moving on, since the next path will now be in its place.
this.#paths.splice(this.#loadedPathIndex + 1, 1);
this.hasErrored = true;
logWarn`Failed to process file size for ${path}: ${error.message}`;
return this.#loadNextPath();
}
this.#sizes.push(size);
this.#loadedPathIndex++;
return this.#loadNextPath();
}
#doneLoadingPaths() {
this.#resolveLoadingPromise();
this.#loadingPromise = null;
this.#resolveLoadingPromise = null;
}
// Override me if you want?
// The rest of the code here is literally just a queue system, so you could
// pretty much repurpose it for anything... but there are probably cleaner
// ways than making an instance or subclass of this and overriding this one
// method!
async readFileSize(path) {
const stats = await stat(path);
return stats.size;
}
getSizeOfPath(path) {
const index = this.#paths.indexOf(path);
if (index === -1) return null;
if (index > this.#loadedPathIndex) return null;
return this.#sizes[index];
}
}
|