refactor to single class

This commit is contained in:
2026-02-15 13:23:46 -08:00
parent f08d2e3d46
commit 08e994f11b
+190 -117
View File
@@ -5,14 +5,6 @@ import { forAsync } from "@sugoidogo/js-util"
const pathSeperatorRegex = /[\\\/]/
export interface PackwizPack {
packDirectoryHandle: FileSystemDirectoryHandle
packFileName: Path
packMetaData: PackMetaData
packIndex: PackIndex
packFiles: { [key: Path]: PackFileMetaData | undefined }
}
function assertOK(response: Response) {
if (!response.ok) {
throw new Error(response.statusText, { "cause": response })
@@ -69,7 +61,7 @@ async function getHash(data: Uint8Array<ArrayBuffer> | string, format: HashForma
throw new Error('unknown hash format: ' + format)
}
async function assertHashMatch(bytes: Uint8Array<ArrayBuffer>, hashFormat: HashFormat, hash: Hash, errorFileName?:string) {
async function assertHashMatch(bytes: Uint8Array<ArrayBuffer>, hashFormat: HashFormat, hash: Hash, errorFileName?: string) {
const bytesHash = await getHash(bytes, hashFormat)
if (bytesHash.toString() !== hash) {
if (errorFileName) throw new Error("hash verification failed on " + errorFileName)
@@ -78,117 +70,198 @@ async function assertHashMatch(bytes: Uint8Array<ArrayBuffer>, hashFormat: HashF
return true
}
export async function fetchPack(packUrl: Url, packDirectoryHandle: FileSystemDirectoryHandle) {
const { packMetaData, packFileName } = await fetch(packUrl).then(async response => {
const packFileText = await assertOK(response).text()
const packMetaData = parseTOML(packFileText) as PackMetaData
const packFileName = packUrl.split(pathSeperatorRegex).pop()
const packFileHandle = await getFileHandle(packDirectoryHandle, packFileName, { "create": true })
await writeFile(packFileHandle, packFileText)
return { packMetaData, packFileName }
})
const packIndexURL = new URL(packMetaData.index.file, packUrl)
const { packIndex, indexDirname: packIndexDirname } = await fetch(packIndexURL).then(async response => {
const packIndexBytes = await assertOK(response).bytes()
await assertHashMatch(packIndexBytes, packMetaData.index["hash-format"], packMetaData.index.hash, packMetaData.index.file)
const packIndex = parseTOML(packIndexBytes.toString()) as PackIndex
const packIndexFileHandle = await getFileHandle(packDirectoryHandle, packMetaData.index.file, { "create": true })
await writeFile(packIndexFileHandle, packIndexBytes)
const packIndexPathSegments = packIndexURL.pathname.split("/")
packIndexPathSegments.pop()
const indexDirname = packIndexPathSegments.join("/")
return { packIndex, indexDirname }
})
const pack: PackwizPack = {
packDirectoryHandle,
packFileName,
packMetaData,
packIndex,
packFiles: {}
}
await forAsync(packIndex.files, async entry => {
const fileHandle = await getFileHandle(packDirectoryHandle, packIndexDirname + "/" + entry.file, { "create": true })
const file = await fileHandle.getFile()
let fileBytes = await file.bytes()
if (await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash)) return
const fileURL = new URL(entry.file, packIndexURL)
fileBytes = await fetch(fileURL)
.then(response => assertOK(response).bytes())
await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash, entry.file)
await writeFile(fileHandle, fileBytes)
if (entry.metafile) pack.packFiles[entry.file] = parseTOML(fileBytes.toString())
})
return pack
}
export class PackwizPack {
export async function loadPack(packDirectoryHandle: FileSystemDirectoryHandle, packFileName = "pack.toml") {
const packMetaData = await getFileHandle(packDirectoryHandle, packFileName).then(async packDirectoryHandle => {
const packFile = await packDirectoryHandle.getFile()
const packFileText = await packFile.text()
return parseTOML(packFileText) as PackMetaData
})
const { packIndex, packIndexDirname } = await getFileHandle(packDirectoryHandle, packMetaData.index.file).then(async indexFileHandle => {
const packIndexFile = await indexFileHandle.getFile()
const packIndexBytes = await packIndexFile.bytes()
if (!packMetaData.options || !packMetaData.options["no-internal-hashes"]) {
await assertHashMatch(packIndexBytes, packMetaData.index["hash-format"], packMetaData.index.hash, packMetaData.index.file)
packDirectoryHandle: FileSystemDirectoryHandle
packFileName: Path
packMetaData: PackMetaData
packIndex: PackIndex
packFiles: { [key: Path]: PackFileMetaData | undefined }
constructor(packDirectoryHandle: FileSystemDirectoryHandle, packName: string, versions:PackMetaData["versions"]) {
this.packDirectoryHandle = packDirectoryHandle
this.packFileName = "pack.toml"
this.packMetaData = {
"name": packName,
"pack-format": "packwiz:1.1.0",
"index": {
"file": "index.toml",
"hash-format": "sha1"
},
"versions": versions
}
const packIndexPathSegments = packMetaData.index.file.split(pathSeperatorRegex)
this.packIndex = {
"hash-format": "sha1",
"files": [] as any
}
this.packFiles = {}
}
static async fetchPack(packDirectoryHandle: FileSystemDirectoryHandle, packUrl: Url) {
const { packMetaData, packFileName } = await fetch(packUrl).then(async response => {
const packFileText = await assertOK(response).text()
const packMetaData = parseTOML(packFileText) as PackMetaData
const packFileName = packUrl.split(pathSeperatorRegex).pop()
const packFileHandle = await getFileHandle(packDirectoryHandle, packFileName, { "create": true })
await writeFile(packFileHandle, packFileText)
return { packMetaData, packFileName }
})
const packIndexURL = new URL(packMetaData.index.file, packUrl)
const { packIndex, indexDirname: packIndexDirname } = await fetch(packIndexURL).then(async response => {
const packIndexBytes = await assertOK(response).bytes()
await assertHashMatch(packIndexBytes, packMetaData.index["hash-format"], packMetaData.index.hash, packMetaData.index.file)
const packIndex = parseTOML(packIndexBytes.toString()) as PackIndex
const packIndexFileHandle = await getFileHandle(packDirectoryHandle, packMetaData.index.file, { "create": true })
await writeFile(packIndexFileHandle, packIndexBytes)
const packIndexPathSegments = packIndexURL.pathname.split("/")
packIndexPathSegments.pop()
const indexDirname = packIndexPathSegments.join("/")
return { packIndex, indexDirname }
})
const pack = Object.assign(new PackwizPack(undefined, undefined, undefined), {
packDirectoryHandle,
packFileName,
packMetaData,
packIndex
})
await forAsync(packIndex.files, async entry => {
const fileHandle = await getFileHandle(packDirectoryHandle, packIndexDirname + "/" + entry.file, { "create": true })
const file = await fileHandle.getFile()
let fileBytes = await file.bytes()
if (await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash)) return
const fileURL = new URL(entry.file, packIndexURL)
fileBytes = await fetch(fileURL)
.then(response => assertOK(response).bytes())
await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash, entry.file)
await writeFile(fileHandle, fileBytes)
if (entry.metafile) pack.packFiles[entry.file] = parseTOML(fileBytes.toString())
})
return pack
}
static async loadPack(packDirectoryHandle: FileSystemDirectoryHandle, packFileName = "pack.toml") {
const packMetaData = await getFileHandle(packDirectoryHandle, packFileName).then(async packDirectoryHandle => {
const packFile = await packDirectoryHandle.getFile()
const packFileText = await packFile.text()
return parseTOML(packFileText) as PackMetaData
})
const { packIndex, packIndexDirname } = await getFileHandle(packDirectoryHandle, packMetaData.index.file).then(async indexFileHandle => {
const packIndexFile = await indexFileHandle.getFile()
const packIndexBytes = await packIndexFile.bytes()
if (!packMetaData.options || !packMetaData.options["no-internal-hashes"]) {
await assertHashMatch(packIndexBytes, packMetaData.index["hash-format"], packMetaData.index.hash, packMetaData.index.file)
}
const packIndexPathSegments = packMetaData.index.file.split(pathSeperatorRegex)
packIndexPathSegments.pop()
const packIndexDirname = packIndexPathSegments.join("/")
const packIndex = parseTOML(packIndexBytes.toString()) as PackIndex
return { packIndex, packIndexDirname }
})
const pack = Object.assign(new PackwizPack(undefined, undefined, undefined), {
packDirectoryHandle,
packFileName,
packMetaData,
packIndex
})
await forAsync(packIndex.files, async entry => {
const fileHandle = await getFileHandle(packDirectoryHandle, packIndexDirname + "/" + entry.file)
const file = await fileHandle.getFile()
const fileBytes = await file.bytes()
if (!packMetaData.options || !packMetaData.options["no-internal-hashes"]) {
await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash, entry.file)
}
if (entry.metafile) pack.packFiles[entry.file] = parseTOML(fileBytes.toString())
})
return pack
}
static async refreshPack(packDirectoryHandle: FileSystemDirectoryHandle, packFileName = "pack.toml") {
const { packMetaData, packFileHandle } = await getFileHandle(packDirectoryHandle, packFileName).then(async packDirectoryHandle => {
const packFile = await packDirectoryHandle.getFile()
const packFileText = await packFile.text()
const packMetaData = parseTOML(packFileText) as PackMetaData
return { packMetaData, packFileHandle }
})
const { packIndex, packIndexDirname, indexFileHandle } = await getFileHandle(packDirectoryHandle, packMetaData.index.file).then(async indexFileHandle => {
const packIndexFile = await indexFileHandle.getFile()
const packIndexBytes = await packIndexFile.bytes()
const packIndexPathSegments = packMetaData.index.file.split(pathSeperatorRegex)
const packIndexDirname = packIndexPathSegments.join("/")
const packIndex = parseTOML(packIndexBytes.toString()) as PackIndex
packIndex.files = [] as any
return { packIndex, packIndexDirname, indexFileHandle }
})
const pack = Object.assign(new PackwizPack(undefined, undefined, undefined), {
packDirectoryHandle,
packFileName,
packMetaData,
packIndex
})
const indexDirectoryHandle = await getDirectoryHandle(packDirectoryHandle, packIndexDirname)
const directoryHandles = [indexDirectoryHandle]
do {
const directoryHandle = directoryHandles.pop()
await forAsync(directoryHandle.values(), async handle => {
if (handle.kind === "directory") {
directoryHandles.push(handle)
return
}
if (handle.isSameEntry(indexFileHandle) || handle.isSameEntry(packFileHandle)) return
const pathSegments = await indexDirectoryHandle.resolve(handle)
const filePath = pathSegments.join("/")
const isMetafile = handle.name.endsWith(".pw.toml")
pack.packIndex.files.push({
"file": filePath,
"metafile": isMetafile
})
if (isMetafile) {
const file = await handle.getFile()
const text = await file.text()
pack.packFiles[filePath] = parseTOML(text)
}
})
} while (directoryHandles.length !== 0)
return pack
}
async savePack(hashes?: boolean) {
if (typeof hashes !== "boolean") {
hashes = !this.packMetaData.options || !this.packMetaData.options["no-internal-hashes"]
}
const textEncoder = new TextEncoder()
const packIndexPathSegments = this.packMetaData.index.file.split(pathSeperatorRegex)
packIndexPathSegments.pop()
const packIndexDirname = packIndexPathSegments.join("/")
const packIndex = parseTOML(packIndexBytes.toString()) as PackIndex
return { packIndex, packIndexDirname }
})
const pack: PackwizPack = {
packDirectoryHandle,
packFileName,
packMetaData,
packIndex,
packFiles: {}
await forAsync(this.packIndex.files, async entry => {
const packFileMetaData = this.packFiles[entry.file]
const fileHandle = await getFileHandle(this.packDirectoryHandle, packIndexDirname + "/" + entry.file, { "create": true })
let packFileBytes: Uint8Array<ArrayBuffer>
if (packFileMetaData) {
packFileBytes = textEncoder.encode(stringifyTOML(packFileMetaData))
await writeFile(fileHandle, packFileBytes)
}
else {
const file = await fileHandle.getFile()
packFileBytes = await file.bytes()
}
if (hashes) {
entry.hash = await getHash(packFileBytes, entry["hash-format"] || this.packIndex["hash-format"])
.then(hash => hash.toString())
} else {
delete entry.hash
}
})
await getFileHandle(this.packDirectoryHandle, this.packMetaData.index.file, { "create": true }).then(async packIndexFileHandle => {
const packIndexBytes = textEncoder.encode(stringifyTOML(this.packIndex))
if (hashes) {
this.packMetaData.index.hash = await getHash(packIndexBytes, this.packMetaData.index["hash-format"])
.then(hash => hash.toString())
} else {
delete this.packMetaData.index.hash
}
await writeFile(packIndexFileHandle, packIndexBytes)
})
await getFileHandle(this.packDirectoryHandle, this.packFileName, { "create": true })
.then(packFileHandle => writeFile(packFileHandle, stringifyTOML(this.packMetaData)))
}
await forAsync(packIndex.files, async entry => {
const fileHandle = await getFileHandle(packDirectoryHandle, packIndexDirname + "/" + entry.file)
const file = await fileHandle.getFile()
const fileBytes = await file.bytes()
if (!packMetaData.options || !packMetaData.options["no-internal-hashes"]) {
await assertHashMatch(fileBytes, entry["hash-format"] || packIndex["hash-format"], entry.hash, entry.file)
}
if (entry.metafile) pack.packFiles[entry.file] = parseTOML(fileBytes.toString())
})
return pack
}
export async function savePack(pack: PackwizPack, hashes?: boolean) {
if (typeof hashes !== "boolean") {
hashes = !pack.packMetaData.options || !pack.packMetaData.options["no-internal-hashes"]
}
const textEncoder = new TextEncoder()
const packIndexPathSegments = pack.packMetaData.index.file.split(pathSeperatorRegex)
packIndexPathSegments.pop()
const packIndexDirname = packIndexPathSegments.join("/")
await forAsync(pack.packIndex.files, async entry => {
const packFileMetaData = pack.packFiles[entry.file]
if (!packFileMetaData) return
const packFileBytes = textEncoder.encode(stringifyTOML(packFileMetaData))
if (hashes) {
entry.hash = await getHash(packFileBytes, entry["hash-format"] || pack.packIndex["hash-format"])
.then(hash => hash.toString())
} else {
delete entry.hash
}
const fileHandle = await getFileHandle(pack.packDirectoryHandle, packIndexDirname + "/" + entry.file, { "create": true })
await writeFile(fileHandle, packFileBytes)
})
await getFileHandle(pack.packDirectoryHandle, pack.packMetaData.index.file, { "create": true }).then(async packIndexFileHandle => {
const packIndexBytes = textEncoder.encode(stringifyTOML(pack.packIndex))
if (hashes) {
pack.packMetaData.index.hash = await getHash(packIndexBytes, pack.packMetaData.index["hash-format"])
.then(hash => hash.toString())
} else {
delete pack.packMetaData.index.hash
}
await writeFile(packIndexFileHandle, packIndexBytes)
})
await getFileHandle(pack.packDirectoryHandle, pack.packFileName, { "create": true })
.then(packFileHandle => writeFile(packFileHandle, stringifyTOML(pack.packMetaData)))
}