-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPopWebZipApi.js
More file actions
200 lines (161 loc) · 5.07 KB
/
PopWebZipApi.js
File metadata and controls
200 lines (161 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// This relies on the NPM package https://github.com/nika-begiashvili/libarchivejs
// gr: moved this to be a submodule... but the worker url isn't relative to the module!
import { Archive as LibArchive } from './libarchive.js/main.js';
import {LoadFileAsArrayBufferAsync,LoadFileAsImageAsync} from './FileSystem.js';
import {CreatePromise,StringToBytes} from './PopApi.js'
// gr: I hate not having the source to hand, but this is the fix for now
// until I can figure out how to get these ES modules out of the damned repos :)
// https://github.com/101arrowz/fflate
import * as fflate from 'https://cdn.skypack.dev/fflate?min';
function GetLibArchiveWorkerUrl()
{
let ModuleUrl = import.meta.url; // this is full http://xxx/x.js url
const ThisUrl = new URL( import.meta.url ); // convert to URL object so it's parsed
let ThisPath = ThisUrl.pathname;
let LastSlash = ThisPath.lastIndexOf('/');
ThisPath = ThisPath.slice(0, LastSlash);
return `${ThisPath}/libarchive.js/dist/worker-bundle.js`;
}
const WorkerUrl = GetLibArchiveWorkerUrl();
LibArchive.init( {
workerUrl: WorkerUrl
} );
export function IsZip(Data)
{
// check header
// maybe need to check more than this?
const PKHeader = [...StringToBytes("PK"),0x03,0x04];
//const PKHeader = [0x50,0x4B,0x03,0x04];
if ( Data instanceof ArrayBuffer )
Data = new Uint8Array( Data, 0, PKHeader.length );
if ( !(Data instanceof Uint8Array) )
throw `IsZip(${typeof Data}) expects uint8array or arraybuffer`;
// gr: should this throw? or just fail
//if ( Data.length < PKHeader.length )
// throw `Not enough data x{Data.length} to verify is PK zip`;
if ( !PKHeader.every( (HeaderByte,i) => Data[i] == HeaderByte ) )
return false;
return true;
}
// todo: merge these together!
export class NewZipArchive
{
constructor()
{
// fflate uses keys for directory structure
this.Files = {};
}
AddFile(Filename,Contents)
{
const FileOptions = {};
//FileOptions.level = 8;
//FileOptions.mtime = new Date('10/20/2020')
const NewFile = [ Contents, FileOptions ];
this.Files[Filename] = NewFile;
}
// return arraybuffer of the compressed archive
GetZipFileData()
{
// there are some async calls, but they need a worker and aren't async/await
// so for now, just using blocking
const Options = {};
const Zipped = fflate.zipSync( this.Files, Options );
const ArchiveData = Zipped;
return ArchiveData;
}
}
export class Archive
{
constructor(ZipFilenameOrBytes)
{
this.OpenPromise = this.Open(ZipFilenameOrBytes);
this.archive = null;
}
async Open(ZipFilenameOrBytes)
{
let Contents;
// load the file if it's a string
if ( typeof ZipFilenameOrBytes == typeof '' )
Contents = await LoadFileAsArrayBufferAsync(ZipFilenameOrBytes);
else
Contents = ZipFilenameOrBytes;
const BlobParams = {};
BlobParams.type = "application/zip";
const ContentsBlob = new Blob([Contents],BlobParams);
this.archive = await LibArchive.open( ContentsBlob );
}
async GetFilenames()
{
await this.OpenPromise;
await this.ExtractFiles();
// get the fulle path of a file entry
function FileToFilePath(File)
{
const CompressedFilename = `${File.path}${File.file.name}`;
return CompressedFilename;
}
const DecompressedArray = await this.archive.getFilesArray()
const Filenames = DecompressedArray.map(FileToFilePath);
return Filenames;
}
async ExtractFiles()
{
return await this.archive.extractFiles();
}
async LoadFileAsStringAsync( FilenameInsideZip )
{
const File = await this.LoadFileAsync( FilenameInsideZip )
async function LoadStringFromReaderAsync()
{
let Promise = CreatePromise();
const reader = new FileReader();
reader.onload = () => Promise.Resolve(reader.result)
reader.onerror = (Error) => Promise.Reject(Error)
reader.readAsText( File )
return Promise;
}
let FileReaderString = await LoadStringFromReaderAsync()
return FileReaderString;
}
async LoadFileAsImageAsync( FilenameInsideZip )
{
const File = await this.LoadFileAsync( FilenameInsideZip )
async function LoadDataURLFromReaderAsync()
{
let Promise = CreatePromise();
const reader = new FileReader();
reader.onload = () => Promise.Resolve( reader.result)
reader.onerror = (Error) => Promise.Reject(Error)
reader.readAsDataURL( File )
return Promise;
}
const FileReaderDataURL = await LoadDataURLFromReaderAsync();
const Img = await LoadFileAsImageAsync(FileReaderDataURL);
return Img;
}
async LoadFileAsArrayBufferAsync(FilenameInsideZip)
{
const File = await this.LoadFileAsync( FilenameInsideZip )
const Buffer = await File.arrayBuffer();
return Buffer;
}
// returns a File object
// expects full path inside archive
async LoadFileAsync(Filename)
{
await this.OpenPromise;
function FileMatch(File)
{
const CompressedFilename = `${File.path}${File.file.name}`;
if ( CompressedFilename != Filename )
return false;
return true;
}
const FileArray = await this.archive.getFilesArray()
const Match = FileArray.find(FileMatch);
if ( !Match )
throw `Failed to find ${Filename} in archive`;
return Match.file;
}
}
export default Archive;