refactor: makes queues extends one class with better error handling

This commit is contained in:
HerozDotExe 2026-06-27 11:39:47 +02:00
parent 4337b2b1d7
commit 9359f0c20a
10 changed files with 151 additions and 225 deletions

View file

@ -47,6 +47,7 @@ export async function AssetsDownloader(
const pool = new DownloadPool(files, {
pQueueOptions: { concurrency: 5 },
overwrite: false,
updateSize: true
});
return pool;

View file

@ -44,6 +44,7 @@ export async function LibrariesDownloader(
const dPool = new DownloadPool(libs, {
pQueueOptions: { concurrency: 5 },
overwrite: false,
updateSize: true
});
return dPool;

View file

@ -240,6 +240,8 @@ export async function ModrinthDownloader(
pQueueOptions: {
concurrency: 5,
},
updateSize: true,
overwrite: true
});
modrinthDownloader.on("completed", () => {
@ -305,16 +307,13 @@ export async function OverridesCopier(
instancePath: string,
emit: Instance["emit"],
) {
const overridesCopyQueue = new CopyQueue(
addedOverrides,
overridesFolder,
instancePath,
{
pQueueOptions: {
concurrency: 5,
},
const overridesCopyQueue = new CopyQueue(addedOverrides, {
pQueueOptions: {
concurrency: 5,
},
);
source: overridesFolder,
destination: instancePath,
});
overridesCopyQueue.on("completed", () => {
emit(
"progress",

View file

@ -9,18 +9,24 @@ import { DownloadAndUnzipPool } from "../utils/unzip";
function getClassifier(library: Library): Native {
switch (os()) {
case "osx":
return library.downloads!.classifiers!["natives-osx"] ||
library.downloads!.classifiers!["natives-macos"];
return (
library.downloads!.classifiers!["natives-osx"] ||
library.downloads!.classifiers!["natives-macos"]
);
case "linux":
return library.downloads!.classifiers!["natives-linux"];
case "windows":
switch (arch()) {
case "x86":
return library.downloads!.classifiers!["natives-windows-32"] ||
library.downloads!.classifiers!["natives-windows"];
return (
library.downloads!.classifiers!["natives-windows-32"] ||
library.downloads!.classifiers!["natives-windows"]
);
case "x64":
return library.downloads!.classifiers!["natives-windows-64"] ||
library.downloads!.classifiers!["natives-windows"];
return (
library.downloads!.classifiers!["natives-windows-64"] ||
library.downloads!.classifiers!["natives-windows"]
);
default:
return library.downloads!.classifiers!["natives-windows"];
}
@ -36,7 +42,7 @@ async function getNatives(versionManifest: Version, tempNativesPath: string) {
if (library.downloads && library.downloads.classifiers) {
if (!isNeeded(library)) continue;
const native = getClassifier(library)
const native = getClassifier(library);
const destination = path.join(
tempNativesPath,
@ -60,18 +66,14 @@ export async function NativesDownloader(
const natives = await getNatives(versionManifest, tempNativesPath);
const pool = new DownloadAndUnzipPool(
natives,
nativesPath,
tempNativesPath,
[".git", "META-INF", ".sha1"],
"skip",
{
pQueueOptions: {
concurrency: 5,
},
const pool = new DownloadAndUnzipPool(natives, nativesPath, tempNativesPath, {
pQueueOptions: {
concurrency: 5,
},
);
overwrite: false,
filters: [".git", "META-INF", ".sha1"],
updateSize: true
});
return pool;
}

View file

@ -42,7 +42,7 @@ export async function importModrinthModpack(
}
await ensureDir(path.join(tempFolder, "pack"), true);
await unzipAll(pack, path.join(tempFolder, "pack"), [], "overwrite");
await unzipAll(pack, path.join(tempFolder, "pack"), [], true);
const manifest = await readJson<ModrinthManifest>(
path.join(tempFolder, "pack", "modrinth.index.json"),
);
@ -53,7 +53,7 @@ export async function importModrinthModpack(
updateFrom,
path.join(tempFolder, "oldPack"),
[],
"overwrite",
true
);
}

View file

@ -1,9 +1,9 @@
import fs from "fs";
import { pipeline } from "stream/promises";
import { PoolFile, PoolOptions } from "./types";
import { DownloadPoolOptions, PoolFile } from "./types";
import { ensureDir, exists } from "./fs";
import path from "path";
import PQueue from "p-queue";
import { Queue } from "./queue";
export async function fetchJson<T>(url: string): Promise<T> {
return await (await fetch(url)).json();
@ -27,47 +27,14 @@ export async function downloadFile(file: PoolFile, overwrite = true) {
}
}
export class DownloadPool extends PQueue {
done = 0;
total = 0;
doneSize = 0;
totalSize = 0;
elements: PoolFile[];
cleanup?: () => Promise<void>;
overwrite: boolean;
export class DownloadPool extends Queue<PoolFile> {
constructor(
elements: PoolFile[],
options: PoolOptions = { pQueueOptions: {}, overwrite: true },
options: DownloadPoolOptions,
) {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
super(options.pQueueOptions);
this.elements = elements;
this.total = this.elements.length;
this.cleanup = options.cleanup;
if (options.overwrite) {
this.overwrite = true;
} else {
this.overwrite = false;
}
super(elements, options);
}
async run() {
for (const element of this.elements) {
this.totalSize += element.size!;
this.add(async () => {
await downloadFile(element, this.overwrite);
// update status here to ensure that it is run before "completed" events listeners
this.done++;
this.doneSize += element.size!;
}).catch(() => {});
}
await Promise.race([this.onError(), this.onIdle()]);
if (this.cleanup) {
await this.cleanup();
}
async action(element: PoolFile, options: DownloadPoolOptions): Promise<void> {
await downloadFile(element, options.overwrite);
}
}

View file

@ -1,7 +1,7 @@
import fs from "fs/promises";
import PQueue from "p-queue";
import { type Options as PQueueOptions } from "p-queue";
import path from "path";
import { Queue } from "./queue";
import { CopyQueueOptions } from "./types";
export async function exists(path: string) {
try {
@ -23,134 +23,32 @@ export async function readJson<T>(destination: string): Promise<T> {
return JSON.parse(await fs.readFile(destination, { encoding: "utf-8" }));
}
export class DeleteQueue extends PQueue {
done = 0;
total = 0;
doneSize = 0;
totalSize = 0;
elements: string[];
cleanup?: () => Promise<void>;
export class DeleteQueue extends Queue<string> {
async action(element: string): Promise<void> {
await fs.rm(element, { recursive: true, force: true });
}
}
export class CopyQueue extends Queue<string> {
constructor(
elements: string[],
options: {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
pQueueOptions: PQueueOptions<null, null>;
cleanup?: () => Promise<void>;
} = {
pQueueOptions: {},
},
options: CopyQueueOptions,
) {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
super(options.pQueueOptions);
this.elements = elements;
this.total = this.elements.length;
this.cleanup = options.cleanup;
super(elements, options);
}
async run() {
for (const element of this.elements) {
this.add(async () => {
await fs.rm(element, { recursive: true, force: true });
this.done++;
}).catch(() => {});
}
await Promise.race([this.onError(), this.onIdle()]);
if (this.cleanup) {
await this.cleanup();
}
async action(element: string, options: CopyQueueOptions): Promise<void> {
await fs.cp(
path.join(options.source, element),
path.join(options.destination, element),
{
force: true,
},
);
}
}
export class CopyQueue extends PQueue {
done = 0;
total = 0;
doneSize = 0;
totalSize = 0;
files: string[];
source: string;
destination: string;
cleanup?: () => Promise<void>;
constructor(
files: string[],
source: string,
destination: string,
options: {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
pQueueOptions: PQueueOptions<null, null>;
cleanup?: () => Promise<void>;
} = {
pQueueOptions: {},
},
) {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
super(options.pQueueOptions);
this.files = files;
this.source = source;
this.destination = destination;
this.total = this.files.length;
this.cleanup = options.cleanup;
}
async run() {
for (const file of this.files) {
this.add(async () => {
await fs.cp(
path.join(this.source, file),
path.join(this.destination, file),
{
force: true,
},
);
this.done++;
}).catch(() => {});
}
await Promise.race([this.onError(), this.onIdle()]);
if (this.cleanup) {
await this.cleanup();
}
}
}
export class TreeMakerQueue extends PQueue {
done = 0;
total = 0;
doneSize = 0;
totalSize = 0;
addedDirs: string[];
cleanup?: () => Promise<void>;
constructor(
addedDirs: string[],
options: {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
pQueueOptions: PQueueOptions<null, null>;
cleanup?: () => Promise<void>;
} = {
pQueueOptions: {},
},
) {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
super(options.pQueueOptions);
this.addedDirs = addedDirs;
this.total = this.addedDirs.length;
this.cleanup = options.cleanup;
}
async run() {
for (const dir of this.addedDirs) {
this.add(async () => {
await ensureDir(dir, true);
this.done++;
}).catch(() => {});
}
await Promise.race([this.onError(), this.onIdle()]);
if (this.cleanup) {
await this.cleanup();
}
export class TreeMakerQueue extends Queue<string> {
async action(element: string): Promise<void> {
await ensureDir(element, true);
}
}

56
src/utils/queue.ts Normal file
View file

@ -0,0 +1,56 @@
import PQueue from "p-queue";
import { PoolOptions } from "./types";
export abstract class Queue<element> extends PQueue {
done = 0;
total = 0;
doneSize = 0;
totalSize = 0;
elements: element[];
options: PoolOptions;
constructor(
elements: element[],
options: PoolOptions = { pQueueOptions: {} },
) {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
super(options.pQueueOptions);
this.elements = elements;
this.total = this.elements.length;
this.options = options;
}
protected abstract action(
element: element,
options: PoolOptions,
): Promise<void>;
async run() {
for (const element of this.elements) {
if (this.options.updateSize) {
// @ts-expect-error this.updateSize === true
this.totalSize += element.size;
this.add(async () => {
await this.action(element, this.options);
// update status here to ensure that it is run before "completed" events listeners
this.done++;
// @ts-expect-error this.updateSize === true
this.doneSize += element.size;
}).catch(() => {});
} else {
this.add(async () => {
await this.action(element, this.options);
// update status here to ensure that it is run before "completed" events listeners
this.done++;
}).catch(() => {});
}
}
await Promise.race([this.onError(), this.onIdle()]);
if (this.options.cleanup) {
await this.options.cleanup();
}
}
}

View file

@ -227,12 +227,25 @@ export type LauncherProfiles = {
};
};
export type PoolOptions = {
export interface PoolOptions {
//@ts-expect-error can't figure out what to put as arguments of PQueueOptions
pQueueOptions: PQueueOptions<null, null>;
pQueueOptions?: PQueueOptions<null, null>;
cleanup?: () => Promise<void>;
updateSize?: boolean;
}
export interface DownloadPoolOptions extends PoolOptions {
overwrite?: boolean;
};
}
export interface DownloadAndUnzipPoolOptions extends DownloadPoolOptions {
filters: string[];
}
export interface CopyQueueOptions extends PoolOptions {
destination: string;
source: string;
}
export interface InstanceEvents {
progress: [

View file

@ -1,10 +1,16 @@
import AdmZip from "adm-zip";
import { PoolFile, PoolOptions } from "./types";
import { downloadFile, DownloadPool } from "./fetch";
import { DownloadAndUnzipPoolOptions, PoolFile } from "./types";
import { downloadFile } from "./fetch";
import path from "path";
import { exists } from "./fs";
import { Queue } from "./queue";
export async function unzipAll(from: string, to: string, filters: string[], mode: "overwrite" | "skip") {
export async function unzipAll(
from: string,
to: string,
filters: string[],
overwrite: boolean,
) {
const zip = new AdmZip(from);
for (const entry of zip.getEntries()) {
@ -15,54 +21,37 @@ export async function unzipAll(from: string, to: string, filters: string[], mode
}
}
if (shouldSkip) continue;
if (!await exists(path.join(to, entry.entryName)) || mode === "overwrite") {
zip.extractEntryTo(entry.entryName, to, true, mode === "overwrite");
if (!(await exists(path.join(to, entry.entryName))) || overwrite) {
zip.extractEntryTo(entry.entryName, to, true, overwrite);
}
}
}
export class DownloadAndUnzipPool extends DownloadPool {
export class DownloadAndUnzipPool extends Queue<PoolFile> {
destination: string;
tempPath: string;
filters: string[];
unzipMode: "overwrite" | "skip"
constructor(
elements: PoolFile[],
destination: string,
tempPath: string,
filters: string[] = [],
unzipMode: "overwrite" | "skip",
options: PoolOptions = { pQueueOptions: {}, overwrite: true },
options: DownloadAndUnzipPoolOptions = {
pQueueOptions: {},
overwrite: true,
filters: [],
},
) {
super(elements, options);
this.tempPath = tempPath;
this.destination = destination;
this.filters = filters;
this.unzipMode = unzipMode
}
async downloadAndUnzip(file: PoolFile, filters: string[], unzipMode: "overwrite" | "skip") {
async action(file: PoolFile, options: DownloadAndUnzipPoolOptions) {
const tempFile = path.join(this.tempPath, path.basename(file.path));
await downloadFile({
url: file.url,
path: tempFile,
});
unzipAll(tempFile, this.destination, filters, unzipMode);
}
async run() {
for (const element of this.elements) {
this.totalSize += element.size!;
this.add(async () => {
await this.downloadAndUnzip(element, this.filters, this.unzipMode);
// update status here to ensure that it is run before "completed" events listeners
this.done++;
this.doneSize += element.size!;
});
}
await this.onIdle();
unzipAll(tempFile, this.destination, options.filters, options.overwrite ?? false);
}
}