5
5
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
6
*/
7
7
import { VirtualDirectory , TreeContainer } from '../metadata-registry' ;
8
- import { join , dirname , basename } from 'path' ;
8
+ import { join , dirname , basename , normalize } from 'path' ;
9
9
import { baseName } from '../utils' ;
10
10
import { parseMetadataXml } from '../utils/registry' ;
11
- import { lstatSync , existsSync , readdirSync , promises as fsPromises } from 'fs' ;
11
+ import { lstatSync , existsSync , readdirSync , promises as fsPromises , createReadStream } from 'fs' ;
12
12
import { LibraryError } from '../errors' ;
13
13
import { SourcePath } from '../common' ;
14
+ import * as unzipper from 'unzipper' ;
15
+ import { Readable } from 'stream' ;
14
16
15
17
/**
16
18
* An extendable base class for implementing the `TreeContainer` interface
@@ -35,6 +37,7 @@ export abstract class BaseTreeContainer implements TreeContainer {
35
37
public abstract isDirectory ( fsPath : SourcePath ) : boolean ;
36
38
public abstract readDirectory ( fsPath : SourcePath ) : string [ ] ;
37
39
public abstract readFile ( fsPath : SourcePath ) : Promise < Buffer > ;
40
+ public abstract stream ( fsPath : SourcePath ) : Readable ;
38
41
}
39
42
40
43
export class NodeFSTreeContainer extends BaseTreeContainer {
@@ -53,6 +56,83 @@ export class NodeFSTreeContainer extends BaseTreeContainer {
53
56
public readFile ( fsPath : SourcePath ) : Promise < Buffer > {
54
57
return fsPromises . readFile ( fsPath ) ;
55
58
}
59
+
60
+ public stream ( fsPath : SourcePath ) : Readable {
61
+ return createReadStream ( fsPath ) ;
62
+ }
63
+ }
64
+
65
+ interface ZipEntry {
66
+ path : string ;
67
+ stream ?: ( ) => unzipper . Entry ;
68
+ buffer ?: ( ) => Promise < Buffer > ;
69
+ }
70
+
71
+ export class ZipTreeContainer extends BaseTreeContainer {
72
+ private tree = new Map < SourcePath , ZipEntry [ ] | ZipEntry > ( ) ;
73
+
74
+ private constructor ( directory : unzipper . CentralDirectory ) {
75
+ super ( ) ;
76
+ this . populate ( directory ) ;
77
+ }
78
+
79
+ public static async create ( buffer : Buffer ) : Promise < ZipTreeContainer > {
80
+ const directory = await unzipper . Open . buffer ( buffer ) ;
81
+ return new ZipTreeContainer ( directory ) ;
82
+ }
83
+
84
+ public exists ( fsPath : string ) : boolean {
85
+ return this . tree . has ( fsPath ) ;
86
+ }
87
+
88
+ public isDirectory ( fsPath : string ) : boolean {
89
+ if ( this . exists ( fsPath ) ) {
90
+ return Array . isArray ( this . tree . get ( fsPath ) ) ;
91
+ }
92
+ throw new LibraryError ( 'error_path_not_found' , fsPath ) ;
93
+ }
94
+
95
+ public readDirectory ( fsPath : string ) : string [ ] {
96
+ if ( this . isDirectory ( fsPath ) ) {
97
+ return ( this . tree . get ( fsPath ) as ZipEntry [ ] ) . map ( ( entry ) => basename ( entry . path ) ) ;
98
+ }
99
+ throw new LibraryError ( 'error_expected_directory_path' , fsPath ) ;
100
+ }
101
+
102
+ public readFile ( fsPath : string ) : Promise < Buffer > {
103
+ if ( ! this . isDirectory ( fsPath ) ) {
104
+ return ( this . tree . get ( fsPath ) as ZipEntry ) . buffer ( ) ;
105
+ }
106
+ throw new LibraryError ( 'error_expected_file_path' , fsPath ) ;
107
+ }
108
+
109
+ public stream ( fsPath : string ) : Readable {
110
+ if ( ! this . isDirectory ( fsPath ) ) {
111
+ return ( this . tree . get ( fsPath ) as ZipEntry ) . stream ( ) ;
112
+ }
113
+ throw new LibraryError ( 'error_no_directory_stream' , this . constructor . name ) ;
114
+ }
115
+
116
+ private populate ( directory : unzipper . CentralDirectory ) : void {
117
+ for ( const { path, stream, buffer } of directory . files ) {
118
+ // normalize path to use OS separator since zip entries always use forward slash
119
+ const entry = { path : normalize ( path ) , stream, buffer } ;
120
+ this . tree . set ( entry . path , entry ) ;
121
+ this . ensureDirPathExists ( entry ) ;
122
+ }
123
+ }
124
+
125
+ private ensureDirPathExists ( entry : ZipEntry ) : void {
126
+ const dirPath = dirname ( entry . path ) ;
127
+ if ( dirPath === entry . path ) {
128
+ return ;
129
+ } else if ( ! this . exists ( dirPath ) ) {
130
+ this . tree . set ( dirPath , [ entry ] ) ;
131
+ this . ensureDirPathExists ( { path : dirPath } ) ;
132
+ } else {
133
+ ( this . tree . get ( dirPath ) as ZipEntry [ ] ) . push ( entry ) ;
134
+ }
135
+ }
56
136
}
57
137
58
138
export class VirtualTreeContainer extends BaseTreeContainer {
@@ -93,6 +173,10 @@ export class VirtualTreeContainer extends BaseTreeContainer {
93
173
throw new LibraryError ( 'error_path_not_found' , fsPath ) ;
94
174
}
95
175
176
+ public stream ( fsPath : string ) : Readable {
177
+ throw new Error ( 'Method not implemented.' ) ;
178
+ }
179
+
96
180
private populate ( virtualFs : VirtualDirectory [ ] ) : void {
97
181
for ( const dir of virtualFs ) {
98
182
const { dirPath, children } = dir ;
0 commit comments