feat: only download assets if not present

This commit is contained in:
HerozDotExe 2026-01-08 11:49:29 +01:00
parent 76f3d7b588
commit bb6bc1dfc7
7 changed files with 55 additions and 36 deletions

View file

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

View file

@ -30,8 +30,8 @@ export async function JavaDownloader(
)
).files;
const componentDestination = path.join(rootDestination, component)
await ensureDir(componentDestination, true)
const componentDestination = path.join(rootDestination, component);
await ensureDir(componentDestination, true);
const files: PoolFile[] = [];
@ -50,10 +50,13 @@ export async function JavaDownloader(
}
}
const pool = new DownloadPool(files, { concurrency: 5 }, async () => {
if (process.platform !== "win32") {
await fs.chmod(path.join(componentDestination, "bin/java"), 0o777);
}
const pool = new DownloadPool(files, {
pQueueOptions: { concurrency: 5 },
cleanup: async () => {
if (process.platform !== "win32") {
await fs.chmod(path.join(componentDestination, "bin/java"), 0o777);
}
},
});
return pool;

View file

@ -28,7 +28,7 @@ export async function LibrariesDownloader(
) {
const libs = getLibraries(versionManifest, librariesRoot);
const dPool = new DownloadPool(libs, { concurrency: 5 });
const dPool = new DownloadPool(libs, { pQueueOptions: { concurrency: 5 } });
return dPool;
}

View file

@ -47,10 +47,12 @@ export async function NativesDownloader(
natives,
nativesPath,
tempNativesPath,
{
concurrency: 5,
},
[".git", "META-INF", ".sha1"],
{
pQueueOptions: {
concurrency: 5,
},
},
);
return pool;

View file

@ -1,29 +1,32 @@
import fs from "fs";
import { pipeline } from "stream/promises";
import { PoolFile } from "./types";
import { ensureDir } from "./fs";
import { PoolFile, PoolOptions } from "./types";
import { ensureDir, exists } from "./fs";
import path from "path";
import PQueue, { Options } from "p-queue";
import PQueue from "p-queue";
export async function fetchJson<T>(url: string): Promise<T> {
return await (await fetch(url)).json();
}
export async function downloadFile(file: PoolFile) {
try {
await ensureDir(path.dirname(file.path), true);
const res = await fetch(file.url);
export async function downloadFile(file: PoolFile, overwrite = true) {
if (overwrite || !(await exists(file.path))) {
try {
await ensureDir(path.dirname(file.path), true);
const res = await fetch(file.url);
if (!res.ok)
throw new Error(
`Failed to download ${file.url}: ${res.status} ${res.statusText}`,
);
await pipeline(res.body, fs.createWriteStream(file.path));
} catch (error) {
if (error.name !== "AbortError") throw error;
return;
if (!res.ok)
throw new Error(
`Failed to download ${file.url}: ${res.status} ${res.statusText}`,
);
await pipeline(res.body, fs.createWriteStream(file.path));
} catch (error) {
if (error.name !== "AbortError") throw error;
return;
}
}
}
export class DownloadPool extends PQueue {
done = 0;
total = 0;
@ -31,16 +34,17 @@ export class DownloadPool extends PQueue {
totalSize = 0;
elements: PoolFile[];
cleanup: () => Promise<void>;
overwrite: boolean;
constructor(
elements: PoolFile[],
options?: Options<null, null>,
cleanup?: () => Promise<void>,
options: PoolOptions = { pQueueOptions: {}, overwrite: true },
) {
super(options);
super(options.pQueueOptions);
this.elements = elements;
this.total = this.elements.length;
this.cleanup = cleanup;
this.cleanup = options.cleanup;
this.overwrite = options.overwrite;
}
async run() {
@ -48,7 +52,7 @@ export class DownloadPool extends PQueue {
this.totalSize += element.size;
this.add(async () => {
await downloadFile(element);
await downloadFile(element, this.overwrite);
// update status here to ensure that it is run before "completed" events listeners
this.done++;
this.doneSize += element.size;
@ -60,4 +64,4 @@ export class DownloadPool extends PQueue {
await this.cleanup();
}
}
}
}

View file

@ -1,3 +1,5 @@
import { type Options as PQueueOptions } from "p-queue";
export type RuntimeComponent =
| "java-runtime-alpha"
| "java-runtime-beta"
@ -212,3 +214,9 @@ export type LauncherProfiles = {
};
};
};
export type PoolOptions = {
pQueueOptions: PQueueOptions<null, null>;
cleanup?: () => Promise<void>;
overwrite?: boolean;
};

View file

@ -1,8 +1,7 @@
import AdmZip from "adm-zip";
import { PoolFile } from "./types";
import { PoolFile, PoolOptions } from "./types";
import { downloadFile, DownloadPool } from "./fetch";
import path from "path";
import { Options } from "p-queue";
export function unzipAll(from: string, to: string, filters: string[]) {
const zip = new AdmZip(from);
@ -12,7 +11,7 @@ export function unzipAll(from: string, to: string, filters: string[]) {
for (const filter of filters) {
if (entry.entryName.includes(filter)) {
shouldSkip = true;
console.log("skipping", entry.entryName)
console.log("skipping", entry.entryName);
}
}
if (shouldSkip) continue;
@ -29,8 +28,8 @@ export class DownloadAndUnzipPool extends DownloadPool {
elements: PoolFile[],
destination: string,
tempPath: string,
options?: Options<null, null>,
filters: string[] = [],
options: PoolOptions = { pQueueOptions: {}, overwrite: true },
) {
super(elements, options);
this.tempPath = tempPath;