Skip to content

Commit

Permalink
Zip contents are now cached (resolves #12)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-pre committed Feb 3, 2025
1 parent 40166b7 commit b5d5a01
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 61 deletions.
71 changes: 24 additions & 47 deletions src/iso/DirectoryRecord.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Errno, ErrnoError } from '@zenfs/core';
import { Errno, ErrnoError, log } from '@zenfs/core';
import { deserialize, member, struct, types as t } from 'utilium';
import { Directory } from './Directory.js';
import { SLComponentFlags } from './SLComponentRecord.js';
Expand Down Expand Up @@ -102,27 +102,23 @@ export class DirectoryRecord {
public fileName(isoData: Uint8Array): string {
if (this.hasRockRidge) {
const fn = this._rockRidgeFilename(isoData);
if (fn != null) {
return fn;
}
if (fn != null) return fn;
}
const ident = this.identifier;
if (this.isDirectory(isoData)) {
return ident;
}
if (this.isDirectory(isoData)) return ident;

// Files:
// - MUST have 0x2E (.) separating the name from the extension
// - MUST have 0x3B (;) separating the file name and extension from the version
// Gets expanded to two-byte char in Unicode directory records.
const versionSeparator = ident.indexOf(';');
if (versionSeparator === -1) {
// Some Joliet filenames lack the version separator, despite the standard specifying that it should be there.
return ident;
}
if (ident[versionSeparator - 1] === '.') {
// Empty extension. Do not include '.' in the filename.
return ident.slice(0, versionSeparator - 1);
}

// Some Joliet filenames lack the version separator, despite the standard specifying that it should be there.
if (versionSeparator === -1) return ident;

// Empty extension. Do not include '.' in the filename.
if (ident[versionSeparator - 1] === '.') return ident.slice(0, versionSeparator - 1);

// Include up to version separator.
return ident.slice(0, versionSeparator);
}
Expand Down Expand Up @@ -175,26 +171,24 @@ export class DirectoryRecord {
}

public getFile(isoData: Uint8Array): Uint8Array {
if (this.isDirectory(isoData)) {
throw new Error('Tried to get a File from a directory.');
}
this._file ||= isoData.slice(this.lba, this.lba + this.dataLength);
if (this.isDirectory(isoData)) throw log.err(ErrnoError.With('EISDIR', undefined, 'read'));
this._file ??= isoData.slice(this.lba, this.lba + this.dataLength);
return this._file;
}

public getDirectory(isoData: Uint8Array): Directory {
if (!this.isDirectory(isoData)) {
throw new Error('Tried to get a Directory from a file.');
}
this._dir ||= this._constructDirectory(isoData);
if (!this.isDirectory(isoData)) throw log.err(ErrnoError.With('ENOTDIR', undefined, 'read'));
this._dir ??= new Directory(this, isoData);
return this._dir;
}

public getSUEntries(isoData: Uint8Array): SystemUseEntry[] {
if (!this._suEntries) {
this._constructSUEntries(isoData);
}
return this._suEntries!;
if (this._suEntries) return this._suEntries;
let i = 33 + this.data[32];
if (i % 2 === 1) i++; // Skip padding fields.
i += this._rockRidgeOffset;
this._suEntries = constructSystemUseEntries(this.data, i, BigInt(this.length), isoData);
return this._suEntries;
}

protected getString(): string {
Expand All @@ -208,35 +202,18 @@ export class DirectoryRecord {
return (data: Uint8Array) => this._decoder!.decode(data).toLowerCase();
}

protected _constructDirectory(isoData: Uint8Array): Directory {
return new Directory(this, isoData);
}

protected _rockRidgeFilename(isoData: Uint8Array): string | null {
const nmEntries = this.getSUEntries(isoData).filter(e => e instanceof NMEntry);
if (nmEntries.length === 0 || nmEntries[0].flags & (NMFlags.CURRENT | NMFlags.PARENT)) {
return null;
}
if (!nmEntries.length || nmEntries[0].flags & (NMFlags.CURRENT | NMFlags.PARENT)) return null;

let str = '';
for (const e of nmEntries) {
str += e.name(this._decode);
if (!(e.flags & NMFlags.CONTINUE)) {
break;
}
if (!(e.flags & NMFlags.CONTINUE)) break;
}
return str;
}

private _constructSUEntries(isoData: Uint8Array): void {
let i = 33 + this.data[32];
if (i % 2 === 1) {
// Skip padding field.
i++;
}
i += this._rockRidgeOffset;
this._suEntries = constructSystemUseEntries(this.data, i, BigInt(this.length), isoData);
}

/**
* !!ONLY VALID ON FIRST ENTRY OF ROOT DIRECTORY!!
* Returns -1 if rock ridge is not enabled. Otherwise, returns the offset
Expand Down
5 changes: 1 addition & 4 deletions src/zip/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,7 @@ export class ZipFS extends Readonly(Sync(FileSystem)) {

public openFileSync(path: string, flag: string): File {
if (isWriteable(flag)) throw new ErrnoError(Errno.EPERM, path);

const stats = this.statSync(path);

return new LazyFile(this, path, flag, stats);
return new LazyFile(this, path, flag, this.statSync(path));
}

public readdirSync(path: string): string[] {
Expand Down
27 changes: 17 additions & 10 deletions src/zip/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,16 @@ export const sizeof_FileEntry = 46;
export class FileEntry<T extends ArrayBufferLike = ArrayBufferLike> {
public constructor(
protected zipData: ArrayBufferLike,
protected _data: T
protected _buffer: T
) {
deserialize(this, _data);
deserialize(this, _buffer);
// Sanity check.
if (this.signature != 0x02014b50) {
throw new ErrnoError(Errno.EINVAL, 'Invalid Zip file: Central directory record has invalid signature: ' + this.signature);
}

this.name = safeDecode(this._data, this.useUTF8, sizeof_FileEntry, this.nameLength).replace(/\\/g, '/');
this.comment = safeDecode(this._data, this.useUTF8, sizeof_FileEntry + this.nameLength + this.extraLength, this.commentLength);
this.name = safeDecode(this._buffer, this.useUTF8, sizeof_FileEntry, this.nameLength).replace(/\\/g, '/');
this.comment = safeDecode(this._buffer, this.useUTF8, sizeof_FileEntry + this.nameLength + this.extraLength, this.commentLength);
}

@t.uint32 public signature!: number;
Expand Down Expand Up @@ -329,7 +329,7 @@ export class FileEntry<T extends ArrayBufferLike = ArrayBufferLike> {
*/
public get extra(): T {
const offset = 44 + this.nameLength;
return this._data.slice(offset, offset + this.extraLength) as T;
return this._buffer.slice(offset, offset + this.extraLength) as T;
}

/**
Expand Down Expand Up @@ -366,11 +366,9 @@ export class FileEntry<T extends ArrayBufferLike = ArrayBufferLike> {
return !this.isDirectory;
}

/**
* Gets the file data, and decompresses it if needed.
* @see http://pkware.com/documents/casestudies/APPNOTE.TXT#:~:text=4.3.8
*/
public get data(): Uint8Array {
protected _data?: Uint8Array;

protected _decompress(): Uint8Array {
// Get the local header before we can figure out where the actual compressed data starts.
const { compressionMethod, size, name } = new LocalFileHeader(this.zipData.slice(this.headerRelativeOffset));
const data = this.zipData.slice(this.headerRelativeOffset + size);
Expand All @@ -383,6 +381,15 @@ export class FileEntry<T extends ArrayBufferLike = ArrayBufferLike> {
return decompress(data, this.compressedSize, this.uncompressedSize, this.flag);
}

/**
* Gets the file data, and decompresses it if needed.
* @see http://pkware.com/documents/casestudies/APPNOTE.TXT#:~:text=4.3.8
*/
public get data(): Uint8Array {
this._data ??= this._decompress();
return this._data;
}

public get stats(): Stats {
return new Stats({
mode: 0o555 | (this.isDirectory ? S_IFDIR : S_IFREG),
Expand Down

0 comments on commit b5d5a01

Please sign in to comment.