Compare commits

...

3 commits

Author SHA1 Message Date
872ea12b3d
feat(sync): add (wip) state file
All checks were successful
Actions / Build and Push Documentation (push) Successful in 17s
2025-02-12 23:59:13 -06:00
75178c54cd
chore(tooling): add hyperfine to the nix flake 2025-02-12 23:58:48 -06:00
fe7916d505
chore(repo): fix husky 2025-02-12 23:58:36 -06:00
4 changed files with 108 additions and 14 deletions

View file

@ -24,6 +24,7 @@
]; ];
packages = with pkgs; [ packages = with pkgs; [
packwiz packwiz
hyperfine
]; ];
shellHook = '' shellHook = ''

View file

@ -12,7 +12,7 @@
"scripts": { "scripts": {
"docs": "typedoc --options typedoc.jsonc", "docs": "typedoc --options typedoc.jsonc",
"format": "bun prettier --write '**/*.{ts,json,md}'", "format": "bun prettier --write '**/*.{ts,json,md}'",
"prepare": "husky || true" "prepare": "bunx husky || true"
}, },
"devDependencies": { "devDependencies": {
"@getmeli/cli": "1.2.0", "@getmeli/cli": "1.2.0",

View file

@ -1 +1 @@
export { iteratePackwizIndex, formatBytes } from "./sync"; export { iteratePackwizIndex, readStateFile, formatBytes } from "./sync";

View file

@ -1,5 +1,5 @@
import { import {
IndexFileEntry, Packwiz,
Resource, Resource,
type Metafile, type Metafile,
type PackwizIndex, type PackwizIndex,
@ -10,13 +10,26 @@ import {
} from "@packwizjs/parser"; } from "@packwizjs/parser";
import { write } from "bun"; import { write } from "bun";
interface HashInfo {
hashFormat: HashFormat;
hash: string;
}
interface State {
stateVersion: number;
currentVersion: string;
hashes: {
[fileName: string]: HashInfo;
};
}
function getSaveLocation( function getSaveLocation(
indexFileEntry: IndexFileEntry, path: Resource,
index: PackwizIndex, index: PackwizIndex,
metafile?: Metafile, metafile?: Metafile,
): Resource { ): Resource {
const cwd = new Resource(process.cwd()); const cwd = new Resource(process.cwd());
const diff = index.location.diff(indexFileEntry.file); const diff = index.location.diff(path);
const fileDirectory = cwd.join(...diff); const fileDirectory = cwd.join(...diff);
if (metafile) { if (metafile) {
return fileDirectory.parent.join(metafile.filename); return fileDirectory.parent.join(metafile.filename);
@ -25,6 +38,50 @@ function getSaveLocation(
} }
} }
async function writeStateFile(
packwiz: Packwiz,
hashes: { [fileName: string]: HashInfo },
) {
const stateFile = packwiz.index.location.parent.join(".packwizjs-state.json");
const state: State = {
stateVersion: 1,
currentVersion: packwiz.version,
hashes: hashes,
};
// for (const file of packwiz.index.files) {
// state.hashes[packwiz.index.location.diff(file.file).join("/")] = {
// hashFormat: file.hashFormat,
// hash: file.hash,
// };
// }
const saveLocation = getSaveLocation(stateFile, packwiz.index);
await write(saveLocation.toString(), JSON.stringify(state, null));
}
/**
* Reads the state file from the specified location.
* @param stateFile - The path to the state file. Defaults to ".packwizjs-state.json" in the current working directory.
* @returns A Promise containing the parsed state object.
*/
export async function readStateFile(
stateFile: Resource = new Resource(process.cwd()).join(
".packwizjs-state.json",
),
): Promise<State> {
// When adding a new state version, do NOT change the name of the stateVersion key!!! That key is NOT versioned.
const json = JSON.parse(await stateFile.fetchContents());
const stateVersion: number = json["stateVersion"];
switch (stateVersion) {
case 1:
const state: State = { ...json };
return state;
default:
throw new Error(
`Unsupported state version: ${stateVersion}. Please update '@packwizjs/sync' to use newer state file versions.`,
);
}
}
async function downloadFile( async function downloadFile(
hash: string, hash: string,
hashFormat: HashFormat, hashFormat: HashFormat,
@ -65,18 +122,27 @@ export function formatBytes(bytes: number, decimals: number = 2) {
* Iterates over the files in a Packwiz index and downloads them concurrently. * Iterates over the files in a Packwiz index and downloads them concurrently.
* @param index The Packwiz index to iterate over. * @param index The Packwiz index to iterate over.
* @param side The side to download files for (e.g., "client", "server", or "both"). * @param side The side to download files for (e.g., "client", "server", or "both").
* @param path The path to save the downloaded files. Defaults to the current working directory.
* @param concurrencyLimit The maximum number of concurrent downloads. * @param concurrencyLimit The maximum number of concurrent downloads.
* @returns A promise that resolves when all files have been downloaded. * @returns A promise that resolves when all files have been downloaded.
*/ */
export async function iteratePackwizIndex( export async function iteratePackwizIndex(
index: PackwizIndex, packwiz: Packwiz,
side: Side, side: Side,
path: Resource = new Resource(process.cwd()),
concurrencyLimit: number = 5, concurrencyLimit: number = 5,
) { ) {
let currentIndex = 0; let currentIndex = 0;
let activeDownloads = 0; let activeDownloads = 0;
const totalFiles = index.files.length; const totalFiles = packwiz.index.files.length;
const stateFile = path.join(".packwizjs-state.json");
let state: State | undefined;
if (await stateFile.exists()) {
state = await readStateFile(stateFile);
}
console.log(state);
side = side.toLowerCase() as Side; side = side.toLowerCase() as Side;
const hashes: { [fileName: string]: HashInfo } = {};
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
function downloadNextFile() { function downloadNextFile() {
@ -89,7 +155,7 @@ export async function iteratePackwizIndex(
// If there is still file entries to download and there are empty concurrency slots, // If there is still file entries to download and there are empty concurrency slots,
// start a new download. // start a new download.
while (activeDownloads < concurrencyLimit && currentIndex < totalFiles) { while (activeDownloads < concurrencyLimit && currentIndex < totalFiles) {
const file = index.files[currentIndex++]; const file = packwiz.index.files[currentIndex++];
activeDownloads++; activeDownloads++;
(async () => { (async () => {
@ -119,18 +185,45 @@ export async function iteratePackwizIndex(
hash = metafile.provider.hash; hash = metafile.provider.hash;
hashFormat = metafile.provider.hashFormat; hashFormat = metafile.provider.hashFormat;
url = metafile.provider.url; url = metafile.provider.url;
saveLocation = getSaveLocation(file, index, metafile); saveLocation = getSaveLocation(
file.file,
packwiz.index,
metafile,
);
} else { } else {
// we don't check non-metafiles' Side because packwiz doesn't store metadata for whether or not to download them on client / server // we don't check non-metafiles' Side because packwiz doesn't store metadata for whether or not to download them on client / server
// so instead, always download them on both sides (assume Side.Both) // so instead, always download them on both sides (assume Side.Both)
const diff = index.location.diff(file.file); const diff = packwiz.index.location.diff(file.file);
hash = file.hash; hash = file.hash;
hashFormat = file.hashFormat; hashFormat = file.hashFormat;
url = index.location.parent.join(...diff); url = packwiz.index.location.parent.join(...diff);
saveLocation = getSaveLocation(file, index); saveLocation = getSaveLocation(file.file, packwiz.index);
} }
if (state) {
let stateHash: HashInfo | undefined;
// TODO: implement hash checking for files downloaded via metafile providers
if (!file.metafile) {
stateHash =
state.hashes[
packwiz.index.location.diff(file.file).join("/")
];
}
if (stateHash?.hash && stateHash.hash === hash) {
console.log(
`Skipping already downloaded file ${file.file.toString()}`,
);
} else {
await downloadFile(hash, hashFormat, url, saveLocation); await downloadFile(hash, hashFormat, url, saveLocation);
}
} else {
await downloadFile(hash, hashFormat, url, saveLocation);
}
hashes[packwiz.index.location.diff(file.file).join("/")] = {
hashFormat: hashFormat,
hash: hash,
};
} catch (error) { } catch (error) {
console.error(`Error downloading file: ${error}`); console.error(`Error downloading file: ${error}`);
reject(error); reject(error);
@ -143,5 +236,5 @@ export async function iteratePackwizIndex(
} }
} }
downloadNextFile(); downloadNextFile();
}); }).then(() => writeStateFile(packwiz, hashes));
} }