11// SPDX-License-Identifier: LGPL-3.0-or-later
2- import { withErrno } from 'kerium' ;
3- import { FileSystem , Inode , type UsageInfo } from '@zenfs/core' ;
2+ import { FileSystem , Inode , Sync , type UsageInfo } from '@zenfs/core' ;
43import type { Backend } from '@zenfs/core/backends/backend.js' ;
54import { Readonly } from '@zenfs/core/mixins/readonly.js' ;
6- import { Sync } from '@zenfs/core/mixins/sync.js' ;
7- import { S_IFDIR } from '@zenfs/core/vfs/constants.js' ;
85import { parse } from '@zenfs/core/path.js' ;
6+ import { S_IFDIR } from '@zenfs/core/vfs/constants.js' ;
7+ import { withErrno } from 'kerium' ;
8+ import { err } from 'kerium/log' ;
99import { _throw } from 'utilium' ;
1010import type { Header } from './zip.js' ;
1111import { computeEOCD , FileEntry } from './zip.js' ;
1212
13+ export interface ZipDataSource < TBuffer extends ArrayBufferLike = ArrayBuffer > {
14+ readonly size : number ;
15+ get ( offset : number , length : number ) : Uint8Array < TBuffer > | Promise < Uint8Array < TBuffer > > ;
16+ set ?( offset : number , data : ArrayBufferView < TBuffer > ) : void | Promise < void > ;
17+ }
18+
1319/**
1420 * Configuration options for a ZipFS file system.
1521 */
1622export interface ZipOptions < TBuffer extends ArrayBufferLike = ArrayBuffer > {
1723 /**
1824 * The zip file as a binary buffer.
1925 */
20- data : TBuffer | ArrayBufferView < TBuffer > ;
26+ data : TBuffer | ArrayBufferView < TBuffer > | ZipDataSource < TBuffer > ;
2127
2228 /**
2329 * The name of the zip file (optional).
@@ -62,16 +68,17 @@ export class ZipFS<TBuffer extends ArrayBufferLike = ArrayBuffer> extends Readon
6268 protected directories : Map < string , Set < string > > = new Map ( ) ;
6369
6470 protected _time = Date . now ( ) ;
71+ private _ready : boolean = false ;
6572
66- protected readonly eocd : Header < TBuffer > ;
73+ protected eocd ! : Header < TBuffer > ;
6774
68- public constructor (
69- public label : string ,
70- protected data : Uint8Array < TBuffer >
71- ) {
72- super ( 0x207a6970 , 'zipfs' ) ;
75+ public async ready ( ) : Promise < void > {
76+ await super . ready ( ) ;
77+
78+ if ( this . _ready ) return ;
79+ this . _ready = true ;
7380
74- this . eocd = computeEOCD ( data ) ;
81+ this . eocd = await computeEOCD ( this . data ) ;
7582 if ( this . eocd . disk != this . eocd . entriesDisk ) {
7683 throw withErrno ( 'EINVAL' , 'ZipFS does not support spanned zip files.' ) ;
7784 }
@@ -84,7 +91,8 @@ export class ZipFS<TBuffer extends ArrayBufferLike = ArrayBuffer> extends Readon
8491 const cdEnd = ptr + this . eocd . size ;
8592
8693 while ( ptr < cdEnd ) {
87- const cd = new FileEntry < TBuffer > ( data . buffer , data . byteOffset + ptr ) ;
94+ const entryData = await this . data . get ( ptr , FileEntry . size ) ;
95+ const cd = new FileEntry < TBuffer > ( entryData . buffer , entryData . byteOffset ) ;
8896 /* Paths must be absolute,
8997 yet zip file paths are always relative to the zip root.
9098 So we prepend '/' and call it a day. */
@@ -122,9 +130,16 @@ export class ZipFS<TBuffer extends ArrayBufferLike = ArrayBuffer> extends Readon
122130 }
123131 }
124132
133+ public constructor (
134+ public label : string ,
135+ protected data : ZipDataSource < TBuffer >
136+ ) {
137+ super ( 0x207a6970 , 'zipfs' ) ;
138+ }
139+
125140 public usage ( ) : UsageInfo {
126141 return {
127- totalSpace : this . data . byteLength ,
142+ totalSpace : this . data . size ,
128143 freeSpace : 0 ,
129144 } ;
130145 }
@@ -170,23 +185,101 @@ export class ZipFS<TBuffer extends ArrayBufferLike = ArrayBuffer> extends Readon
170185 }
171186}
172187
188+ const _isShared = ( b : unknown ) : b is SharedArrayBuffer => typeof b == 'object' && b !== null && b . constructor . name === 'SharedArrayBuffer' ;
189+
190+ export function fromStream ( stream : ReadableStream < Uint8Array > , size : number ) : ZipDataSource < ArrayBuffer > {
191+ const data = new Uint8Array ( size ) ;
192+
193+ let bytesRead = 0 ;
194+ const pending = new Set < {
195+ resolve ( value : void | PromiseLike < void > ) : void ;
196+ offset : number ;
197+ length : number ;
198+ } > ( ) ;
199+
200+ const allDone = ( async function __read ( ) {
201+ for await ( const chunk of stream ) {
202+ data . set ( chunk , bytesRead ) ;
203+ bytesRead += chunk . byteLength ;
204+ for ( const promise of pending ) {
205+ if ( bytesRead >= promise . offset + promise . length ) {
206+ promise . resolve ( ) ;
207+ pending . delete ( promise ) ;
208+ }
209+ }
210+ }
211+ } ) ( ) ;
212+
213+ return {
214+ size,
215+ async get ( offset , length ) {
216+ const view = data . subarray ( offset , offset + length ) ;
217+ if ( bytesRead >= offset + length ) return view ;
218+ const { promise, resolve } = Promise . withResolvers < void > ( ) ;
219+
220+ pending . add ( { resolve, offset, length } ) ;
221+ await promise ;
222+ return view ;
223+ } ,
224+ } ;
225+ }
226+
227+ function getSource < TBuffer extends ArrayBufferLike = ArrayBuffer > ( input : ZipOptions < TBuffer > [ 'data' ] ) : ZipDataSource < TBuffer > {
228+ if ( input instanceof ArrayBuffer || _isShared ( input ) ) {
229+ return {
230+ size : input . byteLength ,
231+ get ( offset : number , length : number ) {
232+ return new Uint8Array ( input , offset , length ) ;
233+ } ,
234+ set ( offset , data ) {
235+ new Uint8Array ( input , offset , data . byteLength ) . set ( new Uint8Array ( data . buffer , data . byteOffset , data . byteLength ) ) ;
236+ } ,
237+ } ;
238+ }
239+
240+ if ( ArrayBuffer . isView ( input ) ) {
241+ return {
242+ size : input . byteLength ,
243+ get ( offset : number , length : number ) {
244+ return new Uint8Array ( input . buffer , input . byteOffset + offset , length ) ;
245+ } ,
246+ set ( offset , data ) {
247+ new Uint8Array ( input . buffer , input . byteOffset + offset , data . byteLength ) . set ( new Uint8Array ( data . buffer , data . byteOffset , data . byteLength ) ) ;
248+ } ,
249+ } ;
250+ }
251+
252+ if ( typeof input == 'object' && input !== null && 'size' in input && typeof input . size == 'number' && typeof input . get == 'function' ) {
253+ return input ;
254+ }
255+
256+ throw err ( withErrno ( 'EINVAL' , 'Invalid zip data source' ) ) ;
257+ }
258+
173259const _Zip = {
174260 name : 'Zip' ,
175261
176262 options : {
177263 data : {
178- type : [ ArrayBuffer , Object . getPrototypeOf ( Uint8Array ) /* %TypedArray% */ ] ,
264+ type : [
265+ ArrayBuffer ,
266+ Object . getPrototypeOf ( Uint8Array ) /* %TypedArray% */ ,
267+ function ZipDataSource ( v : unknown ) : v is ZipDataSource {
268+ return typeof v == 'object' && v !== null && 'size' in v && typeof v . size == 'number' && 'get' in v && typeof v . get == 'function' ;
269+ } ,
270+ ] ,
179271 required : true ,
180272 } ,
181273 name : { type : 'string' , required : false } ,
274+ lazy : { type : 'boolean' , required : false } ,
182275 } ,
183276
184277 isAvailable ( ) : boolean {
185278 return true ;
186279 } ,
187280
188281 create < TBuffer extends ArrayBufferLike = ArrayBuffer > ( { name, data } : ZipOptions < TBuffer > ) : ZipFS < TBuffer > {
189- return new ZipFS < TBuffer > ( name ?? '' , ArrayBuffer . isView ( data ) ? new Uint8Array < TBuffer > ( data . buffer , data . byteOffset , data . byteLength ) : new Uint8Array < TBuffer > ( data ) ) ;
282+ return new ZipFS < TBuffer > ( name ?? '' , getSource ( data ) ) ;
190283 } ,
191284} satisfies Backend < ZipFS , ZipOptions > ;
192285type _Zip = typeof _Zip ;
0 commit comments