« get me outta code hell

file-size-preloader.js « src - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/file-size-preloader.js
blob: d0807cc30afc5b0de0f6d99754348dee765d10c6 (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
// 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 'fs/promises';
import { logWarn } from './util/cli.js';

export default class FileSizePreloader {
    #paths = [];
    #sizes = [];
    #loadedPathIndex = -1;

    #loadingPromise = null;
    #resolveLoadingPromise = null;

    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);
            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];
    }
}