mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-30 05:59:51 +01:00
feat: local vfs
This commit is contained in:
1 parent
b9878a05a1
commit
34ee954502
6 files changed
+89
-42
No files matched your search
@@ -1,10 +1,12 @@
|
|||||||
import { assertSecret } from "@blitzkit/core";
|
import { assertSecret } from "@blitzkit/core";
|
||||||
import { AbstractVFS } from "../core/vfs/abstract";
|
import { AbstractVFS } from "../core/vfs/abstract";
|
||||||
|
import { LocalVFS } from "../core/vfs/local";
|
||||||
import { SteamVFS } from "../core/vfs/steam";
|
import { SteamVFS } from "../core/vfs/steam";
|
||||||
import { ZipVFS } from "../core/vfs/zip";
|
import { ZipVFS } from "../core/vfs/zip";
|
||||||
|
|
||||||
const steamPattern = /steam:(\d+)\/(\d+)/;
|
const steamPattern = /steam:(\d+)\/(\d+)/;
|
||||||
const zipPattern = /zip:(.+)/;
|
const zipPattern = /zip:(.+)/;
|
||||||
|
const localePattern = /local:"?(.+)"?/;
|
||||||
|
|
||||||
const source = Bun.argv
|
const source = Bun.argv
|
||||||
.find((arg) => arg.startsWith("--source="))
|
.find((arg) => arg.startsWith("--source="))
|
||||||
@@ -21,11 +23,14 @@ if (!source) {
|
|||||||
assertSecret(import.meta.env.STEAM_USERNAME),
|
assertSecret(import.meta.env.STEAM_USERNAME),
|
||||||
assertSecret(import.meta.env.STEAM_PASSWORD),
|
assertSecret(import.meta.env.STEAM_PASSWORD),
|
||||||
app,
|
app,
|
||||||
depot
|
depot,
|
||||||
).init();
|
).init();
|
||||||
} else if (zipPattern.test(source)) {
|
} else if (zipPattern.test(source)) {
|
||||||
const [, url] = source.match(zipPattern)!;
|
const [, url] = source.match(zipPattern)!;
|
||||||
resolved = await new ZipVFS(url).init();
|
resolved = await new ZipVFS(url).init();
|
||||||
|
} else if (localePattern.test(source)) {
|
||||||
|
const [, path] = source.match(localePattern)!;
|
||||||
|
resolved = await new LocalVFS(path).init();
|
||||||
} else {
|
} else {
|
||||||
throw new Error("No valid source specified");
|
throw new Error("No valid source specified");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class AssetUploader {
|
|||||||
|
|
||||||
constructor(public message: string) {
|
constructor(public message: string) {
|
||||||
[this.owner, this.repo] = assertSecret(
|
[this.owner, this.repo] = assertSecret(
|
||||||
import.meta.env.PUBLIC_ASSET_REPO
|
import.meta.env.PUBLIC_ASSET_REPO,
|
||||||
).split("/");
|
).split("/");
|
||||||
this.branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
this.branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export class AssetUploader {
|
|||||||
const blobPath = `${this.owner}/${this.repo}/${this.branch}/${change.path}`;
|
const blobPath = `${this.owner}/${this.repo}/${this.branch}/${change.path}`;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://raw.githubusercontent.com/${blobPath}`,
|
`https://raw.githubusercontent.com/${blobPath}`,
|
||||||
{ headers: { "Accept-Encoding": "identity" } }
|
{ headers: { "Accept-Encoding": "identity" } },
|
||||||
);
|
);
|
||||||
let diff: { change: number; status: DiffStatus };
|
let diff: { change: number; status: DiffStatus };
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export class AssetUploader {
|
|||||||
} else if (response.status === 200) {
|
} else if (response.status === 200) {
|
||||||
if (
|
if (
|
||||||
this.heuristicFormats.some((format) =>
|
this.heuristicFormats.some((format) =>
|
||||||
change.path.endsWith(`.${format}`)
|
change.path.endsWith(`.${format}`),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +94,7 @@ export class AssetUploader {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected status code ${response.status} for ${change.path}`
|
`Unexpected status code ${response.status} for ${change.path}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ export class AssetUploader {
|
|||||||
console.log(
|
console.log(
|
||||||
`${diff.status === DiffStatus.New ? "🟢" : "🟡"} (${
|
`${diff.status === DiffStatus.New ? "🟢" : "🟡"} (${
|
||||||
diff.change > 0 ? "+" : ""
|
diff.change > 0 ? "+" : ""
|
||||||
}${diff.change.toLocaleString()}B) ${change.path}`
|
}${diff.change.toLocaleString()}B) ${change.path}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// flush BEFORE going over limit
|
// flush BEFORE going over limit
|
||||||
@@ -160,8 +160,8 @@ export class AssetUploader {
|
|||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
setTimeout(
|
setTimeout(
|
||||||
resolve,
|
resolve,
|
||||||
Math.max(0, this.time_between_blobs + (start - end))
|
Math.max(0, this.time_between_blobs + (start - end)),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
blobs.push({ sha, path: change.path, mode: "100644", type: "blob" });
|
blobs.push({ sha, path: change.path, mode: "100644", type: "blob" });
|
||||||
@@ -170,11 +170,11 @@ export class AssetUploader {
|
|||||||
break;
|
break;
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(
|
console.warn(
|
||||||
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`
|
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
setTimeout(resolve, this.time_per_blob)
|
setTimeout(resolve, this.time_per_blob),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ export class AssetUploader {
|
|||||||
[Symbol.dispose]() {
|
[Symbol.dispose]() {
|
||||||
if (this.changes.length > 0) {
|
if (this.changes.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${this.changes.length} uncommitted changes. Did you forget to flush?`
|
`${this.changes.length} uncommitted changes. Did you forget to flush?`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,24 +11,22 @@ export abstract class AbstractVFS {
|
|||||||
|
|
||||||
abstract dispose(): void;
|
abstract dispose(): void;
|
||||||
|
|
||||||
abstract has(path: string): boolean;
|
abstract has(path: string): Promise<boolean>;
|
||||||
|
|
||||||
abstract raw(path: string): Promise<Uint8Array>;
|
abstract raw(path: string): Promise<Uint8Array>;
|
||||||
|
|
||||||
abstract paths(): string[];
|
async resolve(path: string) {
|
||||||
|
|
||||||
resolve(path: string) {
|
|
||||||
const normalized = normalize(path);
|
const normalized = normalize(path);
|
||||||
if (this.has(normalized)) return normalized;
|
if (await this.has(normalized)) return normalized;
|
||||||
|
|
||||||
const dvplPath = `${normalized}.dvpl`;
|
const dvplPath = `${normalized}.dvpl`;
|
||||||
if (this.has(dvplPath)) return dvplPath;
|
if (await this.has(dvplPath)) return dvplPath;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(path: string) {
|
async assert(path: string) {
|
||||||
const resolved = this.resolve(path);
|
const resolved = await this.resolve(path);
|
||||||
|
|
||||||
if (resolved === null) throw new Error(`File not found: ${path}`);
|
if (resolved === null) throw new Error(`File not found: ${path}`);
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ export abstract class AbstractVFS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async file(file: string) {
|
async file(file: string) {
|
||||||
const resolved = this.assert(file);
|
const resolved = await this.assert(file);
|
||||||
const raw = await this.raw(resolved);
|
const raw = await this.raw(resolved);
|
||||||
let buffer = raw;
|
let buffer = raw;
|
||||||
|
|
||||||
@@ -47,21 +45,7 @@ export abstract class AbstractVFS {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
dir(path: string) {
|
abstract dir(path: string): Promise<string[]>;
|
||||||
const parentSegments = path.split("/").length;
|
|
||||||
const children = new Set<string>();
|
|
||||||
|
|
||||||
for (const child of this.paths()) {
|
|
||||||
if (!child.startsWith(path) || child === path) continue;
|
|
||||||
|
|
||||||
const childSegments = child.split("/");
|
|
||||||
const nextSegment = childSegments[parentSegments];
|
|
||||||
|
|
||||||
children.add(nextSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(children);
|
|
||||||
}
|
|
||||||
|
|
||||||
async text(path: string) {
|
async text(path: string) {
|
||||||
const file = await this.file(path);
|
const file = await this.file(path);
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import { AbstractVFS } from "./abstract";
|
||||||
|
|
||||||
|
export class LocalVFS extends AbstractVFS {
|
||||||
|
constructor(private base: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async has(path: string) {
|
||||||
|
const file = Bun.file(`${this.base}/${path}`);
|
||||||
|
return await file.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
async raw(path: string) {
|
||||||
|
const file = Bun.file(`${this.base}/${path}`);
|
||||||
|
const buffer = await file.arrayBuffer();
|
||||||
|
const array = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
async dir(path: string) {
|
||||||
|
return await readdir(`${this.base}/${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {}
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ export class SteamVFS extends AbstractVFS {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
has(path: string) {
|
async has(path: string) {
|
||||||
return this.manifest.has(path);
|
return this.manifest.has(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +89,20 @@ export class SteamVFS extends AbstractVFS {
|
|||||||
return new Uint8Array(downloaded.file);
|
return new Uint8Array(downloaded.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
paths() {
|
async dir(path: string) {
|
||||||
return Array.from(this.manifest.keys());
|
const parentSegments = path.split("/").length;
|
||||||
|
const children = new Set<string>();
|
||||||
|
|
||||||
|
for (const child of this.manifest.keys()) {
|
||||||
|
if (!child.startsWith(path) || child === path) continue;
|
||||||
|
|
||||||
|
const childSegments = child.split("/");
|
||||||
|
const nextSegment = childSegments[parentSegments];
|
||||||
|
|
||||||
|
children.add(nextSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { AbstractVFS } from "./abstract";
|
|||||||
export class ZipVFS extends AbstractVFS {
|
export class ZipVFS extends AbstractVFS {
|
||||||
entries = new Map<string, ZipEntry>();
|
entries = new Map<string, ZipEntry>();
|
||||||
|
|
||||||
constructor(private url: string, private root = "app/") {
|
constructor(
|
||||||
|
private url: string,
|
||||||
|
private root = "app/",
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ export class ZipVFS extends AbstractVFS {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
has(path: string) {
|
async has(path: string) {
|
||||||
return this.entries.has(path);
|
return this.entries.has(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +33,20 @@ export class ZipVFS extends AbstractVFS {
|
|||||||
return new Uint8Array(arrayBuffer);
|
return new Uint8Array(arrayBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
paths() {
|
async dir(path: string) {
|
||||||
return Array.from(this.entries.keys());
|
const parentSegments = path.split("/").length;
|
||||||
|
const children = new Set<string>();
|
||||||
|
|
||||||
|
for (const child of this.entries.keys()) {
|
||||||
|
if (!child.startsWith(path) || child === path) continue;
|
||||||
|
|
||||||
|
const childSegments = child.split("/");
|
||||||
|
const nextSegment = childSegments[parentSegments];
|
||||||
|
|
||||||
|
children.add(nextSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {}
|
dispose() {}
|
||||||
|
|||||||
Reference in new issue
Block a user