-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathwrite.js
More file actions
174 lines (148 loc) · 4.83 KB
/
write.js
File metadata and controls
174 lines (148 loc) · 4.83 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
/**
* Conflux
* Build (and read) zip files with whatwg streams in the browser.
*
* @author Transcend Inc. <https://transcend.io>
* @license MIT
*/
import JSBI from './bigint.js';
import Crc32 from './crc.js';
const encoder = new TextEncoder();
class ZipTransformer {
constructor() {
/* The files zipped */
this.files = Object.create(null);
/* The current position of the zipped output stream, in bytes */
this.offset = JSBI.BigInt(0);
}
/**
* Transforms a stream of files into one zipped file
*
* @param {File} entry [description]
* @param {ReadableStreamDefaultController} ctrl
* @return {Promise} [description]
*/
async transform(entry, ctrl) {
// Set the File name, ensuring that if it's a directory, it ends with `/`
const name =
entry.directory && !entry.name.trim().endsWith('/')
? `${entry.name.trim()}/`
: entry.name.trim();
// Abort if this a file with this name already exists
if (this.files[name]) ctrl.abort(new Error('File already exists.'));
// TextEncode the name
const nameBuf = encoder.encode(name);
this.files[name] = {
directory: !!entry.directory,
nameBuf,
offset: this.offset,
comment: encoder.encode(entry.comment || ''),
compressedLength: JSBI.BigInt(0),
uncompressedLength: JSBI.BigInt(0),
header: new Uint8Array(26),
};
const zipObject = this.files[name];
const { header } = zipObject;
// Set the date, with fallback to current date
const date = new Date(
typeof entry.lastModified === 'undefined'
? Date.now()
: entry.lastModified,
);
// The File header DataView
const hdv = new DataView(header.buffer);
const data = new Uint8Array(30 + nameBuf.length);
hdv.setUint32(0, 0x14000808);
hdv.setUint16(
6,
(((date.getHours() << 6) | date.getMinutes()) << 5) |
(date.getSeconds() / 2),
true,
);
hdv.setUint16(
8,
((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) |
date.getDate(),
true,
);
hdv.setUint16(22, nameBuf.length, true);
data.set([80, 75, 3, 4]);
data.set(header, 4);
data.set(nameBuf, 30);
this.offset = JSBI.add(this.offset, JSBI.BigInt(data.length));
ctrl.enqueue(data);
const footer = new Uint8Array(16);
footer.set([80, 75, 7, 8]);
if (entry.stream) {
zipObject.crc = new Crc32();
const reader = entry.stream().getReader();
// eslint-disable-next-line no-constant-condition
while (true) {
const it = await reader.read();
if (it.done) break;
const chunk = it.value;
zipObject.crc.append(chunk);
zipObject.uncompressedLength = JSBI.add(
zipObject.uncompressedLength,
JSBI.BigInt(chunk.length),
);
zipObject.compressedLength = JSBI.add(
zipObject.compressedLength,
JSBI.BigInt(chunk.length),
);
ctrl.enqueue(chunk);
}
hdv.setUint32(10, zipObject.crc.get(), true);
hdv.setUint32(14, JSBI.toNumber(zipObject.compressedLength), true);
hdv.setUint32(18, JSBI.toNumber(zipObject.uncompressedLength), true);
footer.set(header.subarray(10, 22), 4);
}
hdv.setUint16(22, nameBuf.length, true);
this.offset = JSBI.add(
this.offset,
JSBI.add(zipObject.compressedLength, JSBI.BigInt(16)),
);
ctrl.enqueue(footer);
}
/**
* @param {ReadableStreamDefaultController} ctrl
*/
flush(ctrl) {
let length = 0;
let index = 0;
let file;
Object.keys(this.files).forEach((fileName) => {
file = this.files[fileName];
length += 46 + file.nameBuf.length + file.comment.length;
});
const data = new Uint8Array(length + 22);
const dv = new DataView(data.buffer);
Object.keys(this.files).forEach((fileName) => {
file = this.files[fileName];
dv.setUint32(index, 0x504b0102);
dv.setUint16(index + 4, 0x1400);
dv.setUint16(index + 32, file.comment.length, true);
dv.setUint8(index + 38, file.directory ? 16 : 0);
dv.setUint32(index + 42, JSBI.toNumber(file.offset), true);
data.set(file.header, index + 6);
data.set(file.nameBuf, index + 46);
data.set(file.comment, index + 46 + file.nameBuf.length);
index += 46 + file.nameBuf.length + file.comment.length;
});
dv.setUint32(index, 0x504b0506);
dv.setUint16(index + 8, Object.keys(this.files).length, true);
dv.setUint16(index + 10, Object.keys(this.files).length, true);
dv.setUint32(index + 12, length, true);
dv.setUint32(index + 16, JSBI.toNumber(this.offset), true);
ctrl.enqueue(data);
// cleanup
this.files = Object.create(null);
this.offset = 0;
}
}
class Writer extends TransformStream {
constructor() {
super(new ZipTransformer());
}
}
export default Writer;