@push.rocks/smartarchive 4.2.4 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/bzip2/bititerator.d.ts +6 -1
- package/dist_ts/bzip2/bititerator.js +30 -24
- package/dist_ts/bzip2/bzip2.d.ts +27 -12
- package/dist_ts/bzip2/bzip2.js +325 -263
- package/dist_ts/bzip2/index.d.ts +4 -1
- package/dist_ts/bzip2/index.js +36 -38
- package/dist_ts/classes.archiveanalyzer.d.ts +25 -5
- package/dist_ts/classes.archiveanalyzer.js +19 -8
- package/dist_ts/classes.bzip2tools.d.ts +1 -1
- package/dist_ts/classes.gziptools.d.ts +43 -6
- package/dist_ts/classes.gziptools.js +76 -24
- package/dist_ts/classes.smartarchive.d.ts +198 -16
- package/dist_ts/classes.smartarchive.js +652 -79
- package/dist_ts/classes.tartools.d.ts +31 -4
- package/dist_ts/classes.tartools.js +93 -32
- package/dist_ts/classes.ziptools.d.ts +47 -12
- package/dist_ts/classes.ziptools.js +128 -23
- package/dist_ts/errors.d.ts +44 -0
- package/dist_ts/errors.js +62 -0
- package/dist_ts/index.d.ts +4 -0
- package/dist_ts/index.js +9 -1
- package/dist_ts/interfaces.d.ts +119 -0
- package/dist_ts/interfaces.js +2 -0
- package/package.json +1 -1
- package/readme.hints.md +69 -23
- package/readme.md +360 -274
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/bzip2/bititerator.ts +43 -27
- package/ts/bzip2/bzip2.ts +289 -171
- package/ts/bzip2/index.ts +52 -50
- package/ts/classes.archiveanalyzer.ts +42 -28
- package/ts/classes.gziptools.ts +91 -33
- package/ts/classes.smartarchive.ts +765 -140
- package/ts/classes.tartools.ts +124 -52
- package/ts/classes.ziptools.ts +147 -34
- package/ts/errors.ts +70 -0
- package/ts/index.ts +11 -0
- package/ts/interfaces.ts +136 -0
package/ts/bzip2/index.ts
CHANGED
|
@@ -1,51 +1,53 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
+
import { Bzip2Error, BZIP2_ERROR_CODES } from '../errors.js';
|
|
3
|
+
import type { IBitReader } from '../interfaces.js';
|
|
2
4
|
|
|
3
5
|
import { Bzip2 } from './bzip2.js';
|
|
4
6
|
import { bitIterator } from './bititerator.js';
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Creates a streaming BZIP2 decompression transform
|
|
10
|
+
*/
|
|
11
|
+
export function unbzip2Stream(): plugins.smartstream.SmartDuplex<Buffer, Buffer> {
|
|
7
12
|
const bzip2Instance = new Bzip2();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
var streamCRC = null;
|
|
13
|
+
const bufferQueue: Buffer[] = [];
|
|
14
|
+
let hasBytes = 0;
|
|
15
|
+
let blockSize = 0;
|
|
16
|
+
let broken = false;
|
|
17
|
+
let bitReader: IBitReader | null = null;
|
|
18
|
+
let streamCRC: number | null = null;
|
|
15
19
|
|
|
16
|
-
function decompressBlock() {
|
|
20
|
+
function decompressBlock(): Buffer | undefined {
|
|
17
21
|
if (!blockSize) {
|
|
18
|
-
blockSize = bzip2Instance.header(bitReader);
|
|
22
|
+
blockSize = bzip2Instance.header(bitReader!);
|
|
19
23
|
streamCRC = 0;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var buf = new Int32Array(bufsize);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
27
|
+
const bufsize = 100000 * blockSize;
|
|
28
|
+
const buf = new Int32Array(bufsize);
|
|
29
|
+
const chunk: number[] = [];
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return;
|
|
40
|
-
} else {
|
|
41
|
-
return Buffer.from(chunk);
|
|
42
|
-
}
|
|
31
|
+
const outputFunc = (b: number): void => {
|
|
32
|
+
chunk.push(b);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
streamCRC = bzip2Instance.decompress(bitReader!, outputFunc, buf, bufsize, streamCRC);
|
|
36
|
+
|
|
37
|
+
if (streamCRC === null) {
|
|
38
|
+
// Reset for next bzip2 header
|
|
39
|
+
blockSize = 0;
|
|
40
|
+
return undefined;
|
|
43
41
|
}
|
|
42
|
+
|
|
43
|
+
return Buffer.from(chunk);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
let outlength = 0;
|
|
47
|
+
|
|
48
|
+
const decompressAndPush = async (): Promise<Buffer | undefined> => {
|
|
49
|
+
if (broken) return undefined;
|
|
50
|
+
|
|
49
51
|
try {
|
|
50
52
|
const resultChunk = decompressBlock();
|
|
51
53
|
if (resultChunk) {
|
|
@@ -53,40 +55,39 @@ export function unbzip2Stream() {
|
|
|
53
55
|
}
|
|
54
56
|
return resultChunk;
|
|
55
57
|
} catch (e) {
|
|
56
|
-
console.error(e);
|
|
57
58
|
broken = true;
|
|
59
|
+
if (e instanceof Error) {
|
|
60
|
+
throw new Bzip2Error(`Decompression failed: ${e.message}`, BZIP2_ERROR_CODES.INVALID_BLOCK_DATA);
|
|
61
|
+
}
|
|
62
|
+
throw e;
|
|
58
63
|
}
|
|
59
64
|
};
|
|
60
|
-
|
|
61
|
-
return new plugins.smartstream.SmartDuplex({
|
|
65
|
+
|
|
66
|
+
return new plugins.smartstream.SmartDuplex<Buffer, Buffer>({
|
|
62
67
|
objectMode: true,
|
|
63
68
|
name: 'bzip2',
|
|
64
|
-
debug: false,
|
|
65
69
|
highWaterMark: 1,
|
|
66
70
|
writeFunction: async function (data, streamTools) {
|
|
67
|
-
// console.log(`got chunk ${counter++}`)
|
|
68
71
|
bufferQueue.push(data);
|
|
69
72
|
hasBytes += data.length;
|
|
73
|
+
|
|
70
74
|
if (bitReader === null) {
|
|
71
75
|
bitReader = bitIterator(function () {
|
|
72
|
-
return bufferQueue.shift()
|
|
76
|
+
return bufferQueue.shift()!;
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
) {
|
|
79
|
-
//console.error('decompressing with', hasBytes - bitReader.bytesRead + 1, 'bytes in buffer');
|
|
79
|
+
|
|
80
|
+
const threshold = 25000 + 100000 * blockSize || 4;
|
|
81
|
+
while (!broken && hasBytes - bitReader.bytesRead + 1 >= threshold) {
|
|
80
82
|
const result = await decompressAndPush();
|
|
81
83
|
if (!result) {
|
|
82
84
|
continue;
|
|
83
85
|
}
|
|
84
|
-
// console.log(result.toString());
|
|
85
86
|
await streamTools.push(result);
|
|
86
87
|
}
|
|
88
|
+
return null;
|
|
87
89
|
},
|
|
88
90
|
finalFunction: async function (streamTools) {
|
|
89
|
-
//console.error(x,'last compressing with', hasBytes, 'bytes in buffer');
|
|
90
91
|
while (!broken && bitReader && hasBytes > bitReader.bytesRead) {
|
|
91
92
|
const result = await decompressAndPush();
|
|
92
93
|
if (!result) {
|
|
@@ -94,10 +95,11 @@ export function unbzip2Stream() {
|
|
|
94
95
|
}
|
|
95
96
|
await streamTools.push(result);
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
|
|
99
|
+
if (!broken && streamCRC !== null) {
|
|
100
|
+
this.emit('error', new Bzip2Error('Input stream ended prematurely', BZIP2_ERROR_CODES.PREMATURE_END));
|
|
100
101
|
}
|
|
102
|
+
return null;
|
|
101
103
|
},
|
|
102
104
|
});
|
|
103
105
|
}
|
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
import type { SmartArchive } from './classes.smartarchive.js';
|
|
2
|
+
import type { TSupportedMime } from './interfaces.js';
|
|
2
3
|
import * as plugins from './plugins.js';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Type for decompression streams
|
|
7
|
+
*/
|
|
8
|
+
export type TDecompressionStream =
|
|
9
|
+
| plugins.stream.Transform
|
|
10
|
+
| plugins.stream.Duplex
|
|
11
|
+
| plugins.tarStream.Extract;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Result of archive analysis
|
|
15
|
+
*/
|
|
4
16
|
export interface IAnalyzedResult {
|
|
5
|
-
fileType: plugins.fileType.FileTypeResult;
|
|
17
|
+
fileType: plugins.fileType.FileTypeResult | undefined;
|
|
6
18
|
isArchive: boolean;
|
|
7
|
-
resultStream: plugins.smartstream.SmartDuplex
|
|
8
|
-
decompressionStream:
|
|
9
|
-
| plugins.stream.Transform
|
|
10
|
-
| plugins.stream.Duplex
|
|
11
|
-
| plugins.tarStream.Extract;
|
|
19
|
+
resultStream: plugins.smartstream.SmartDuplex<Buffer, Buffer>;
|
|
20
|
+
decompressionStream: TDecompressionStream;
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Analyzes archive streams to detect format and provide decompression
|
|
25
|
+
*/
|
|
14
26
|
export class ArchiveAnalyzer {
|
|
15
|
-
smartArchiveRef: SmartArchive;
|
|
27
|
+
private smartArchiveRef: SmartArchive;
|
|
16
28
|
|
|
17
29
|
constructor(smartArchiveRefArg: SmartArchive) {
|
|
18
30
|
this.smartArchiveRef = smartArchiveRefArg;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Check if a MIME type represents an archive format
|
|
35
|
+
*/
|
|
36
|
+
private async mimeTypeIsArchive(mimeType: string | undefined): Promise<boolean> {
|
|
37
|
+
if (!mimeType) return false;
|
|
38
|
+
|
|
22
39
|
const archiveMimeTypes: Set<string> = new Set([
|
|
23
40
|
'application/zip',
|
|
24
41
|
'application/x-rar-compressed',
|
|
@@ -26,50 +43,46 @@ export class ArchiveAnalyzer {
|
|
|
26
43
|
'application/gzip',
|
|
27
44
|
'application/x-7z-compressed',
|
|
28
45
|
'application/x-bzip2',
|
|
29
|
-
// Add other archive mime types here
|
|
30
46
|
]);
|
|
31
47
|
|
|
32
48
|
return archiveMimeTypes.has(mimeType);
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
> {
|
|
51
|
+
/**
|
|
52
|
+
* Get the appropriate decompression stream for a MIME type
|
|
53
|
+
*/
|
|
54
|
+
private async getDecompressionStream(mimeTypeArg: TSupportedMime): Promise<TDecompressionStream> {
|
|
40
55
|
switch (mimeTypeArg) {
|
|
41
56
|
case 'application/gzip':
|
|
42
57
|
return this.smartArchiveRef.gzipTools.getDecompressionStream();
|
|
43
58
|
case 'application/zip':
|
|
44
59
|
return this.smartArchiveRef.zipTools.getDecompressionStream();
|
|
45
60
|
case 'application/x-bzip2':
|
|
46
|
-
return
|
|
61
|
+
return this.smartArchiveRef.bzip2Tools.getDecompressionStream();
|
|
47
62
|
case 'application/x-tar':
|
|
48
|
-
return this.smartArchiveRef.tarTools.getDecompressionStream();
|
|
63
|
+
return this.smartArchiveRef.tarTools.getDecompressionStream();
|
|
49
64
|
default:
|
|
50
65
|
// Handle unsupported formats or no decompression needed
|
|
51
66
|
return plugins.smartstream.createPassThrough();
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Create an analyzed stream that detects archive type and provides decompression
|
|
72
|
+
* Emits a single IAnalyzedResult object
|
|
73
|
+
*/
|
|
74
|
+
public getAnalyzedStream(): plugins.smartstream.SmartDuplex<Buffer, IAnalyzedResult> {
|
|
56
75
|
let firstRun = true;
|
|
57
76
|
const resultStream = plugins.smartstream.createPassThrough();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
IAnalyzedResult
|
|
61
|
-
>({
|
|
77
|
+
|
|
78
|
+
const analyzerstream = new plugins.smartstream.SmartDuplex<Buffer, IAnalyzedResult>({
|
|
62
79
|
readableObjectMode: true,
|
|
63
80
|
writeFunction: async (chunkArg: Buffer, streamtools) => {
|
|
64
81
|
if (firstRun) {
|
|
65
82
|
firstRun = false;
|
|
66
83
|
const fileType = await plugins.fileType.fileTypeFromBuffer(chunkArg);
|
|
67
|
-
const decompressionStream = await this.getDecompressionStream(
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
/**
|
|
71
|
-
* analyzed stream emits once with this object
|
|
72
|
-
*/
|
|
84
|
+
const decompressionStream = await this.getDecompressionStream(fileType?.mime as TSupportedMime);
|
|
85
|
+
|
|
73
86
|
const result: IAnalyzedResult = {
|
|
74
87
|
fileType,
|
|
75
88
|
isArchive: await this.mimeTypeIsArchive(fileType?.mime),
|
|
@@ -81,11 +94,12 @@ export class ArchiveAnalyzer {
|
|
|
81
94
|
await resultStream.backpressuredPush(chunkArg);
|
|
82
95
|
return null;
|
|
83
96
|
},
|
|
84
|
-
finalFunction: async (
|
|
97
|
+
finalFunction: async () => {
|
|
85
98
|
resultStream.push(null);
|
|
86
99
|
return null;
|
|
87
100
|
},
|
|
88
101
|
});
|
|
102
|
+
|
|
89
103
|
return analyzerstream;
|
|
90
104
|
}
|
|
91
105
|
}
|
package/ts/classes.gziptools.ts
CHANGED
|
@@ -1,42 +1,60 @@
|
|
|
1
|
-
import type { SmartArchive } from './classes.smartarchive.js';
|
|
2
1
|
import * as plugins from './plugins.js';
|
|
2
|
+
import type { TCompressionLevel } from './interfaces.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Transform stream for GZIP compression using fflate
|
|
6
|
+
*/
|
|
7
|
+
export class GzipCompressionTransform extends plugins.stream.Transform {
|
|
8
|
+
private gzip: plugins.fflate.Gzip;
|
|
9
|
+
|
|
10
|
+
constructor(level: TCompressionLevel = 6) {
|
|
7
11
|
super();
|
|
12
|
+
|
|
13
|
+
// Create a streaming Gzip compressor
|
|
14
|
+
this.gzip = new plugins.fflate.Gzip({ level }, (chunk, final) => {
|
|
15
|
+
this.push(Buffer.from(chunk));
|
|
16
|
+
if (final) {
|
|
17
|
+
this.push(null);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
8
20
|
}
|
|
9
21
|
|
|
10
22
|
_transform(
|
|
11
23
|
chunk: Buffer,
|
|
12
24
|
encoding: BufferEncoding,
|
|
13
|
-
callback: plugins.stream.TransformCallback
|
|
14
|
-
) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
callback: plugins.stream.TransformCallback
|
|
26
|
+
): void {
|
|
27
|
+
try {
|
|
28
|
+
this.gzip.push(chunk, false);
|
|
29
|
+
callback();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
callback(err as Error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_flush(callback: plugins.stream.TransformCallback): void {
|
|
36
|
+
try {
|
|
37
|
+
this.gzip.push(new Uint8Array(0), true);
|
|
38
|
+
callback();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
callback(err as Error);
|
|
41
|
+
}
|
|
23
42
|
}
|
|
24
43
|
}
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Transform stream for GZIP decompression using fflate
|
|
47
|
+
*/
|
|
48
|
+
export class GzipDecompressionTransform extends plugins.stream.Transform {
|
|
49
|
+
private gunzip: plugins.fflate.Gunzip;
|
|
50
|
+
|
|
31
51
|
constructor() {
|
|
32
52
|
super();
|
|
33
|
-
|
|
53
|
+
|
|
34
54
|
// Create a streaming Gunzip decompressor
|
|
35
55
|
this.gunzip = new plugins.fflate.Gunzip((chunk, final) => {
|
|
36
|
-
// Push decompressed chunks to the output stream
|
|
37
56
|
this.push(Buffer.from(chunk));
|
|
38
57
|
if (final) {
|
|
39
|
-
// Signal end of stream when decompression is complete
|
|
40
58
|
this.push(null);
|
|
41
59
|
}
|
|
42
60
|
});
|
|
@@ -45,20 +63,18 @@ export class DecompressGunzipTransform extends plugins.stream.Transform {
|
|
|
45
63
|
_transform(
|
|
46
64
|
chunk: Buffer,
|
|
47
65
|
encoding: BufferEncoding,
|
|
48
|
-
callback: plugins.stream.TransformCallback
|
|
49
|
-
) {
|
|
66
|
+
callback: plugins.stream.TransformCallback
|
|
67
|
+
): void {
|
|
50
68
|
try {
|
|
51
|
-
// Feed chunks to the gunzip stream
|
|
52
69
|
this.gunzip.push(chunk, false);
|
|
53
70
|
callback();
|
|
54
71
|
} catch (err) {
|
|
55
72
|
callback(err as Error);
|
|
56
73
|
}
|
|
57
74
|
}
|
|
58
|
-
|
|
59
|
-
_flush(callback: plugins.stream.TransformCallback) {
|
|
75
|
+
|
|
76
|
+
_flush(callback: plugins.stream.TransformCallback): void {
|
|
60
77
|
try {
|
|
61
|
-
// Signal end of input to gunzip
|
|
62
78
|
this.gunzip.push(new Uint8Array(0), true);
|
|
63
79
|
callback();
|
|
64
80
|
} catch (err) {
|
|
@@ -67,14 +83,56 @@ export class DecompressGunzipTransform extends plugins.stream.Transform {
|
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
|
|
86
|
+
/**
|
|
87
|
+
* GZIP compression and decompression utilities
|
|
88
|
+
*/
|
|
70
89
|
export class GzipTools {
|
|
71
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Get a streaming compression transform
|
|
92
|
+
*/
|
|
93
|
+
public getCompressionStream(level?: TCompressionLevel): plugins.stream.Transform {
|
|
94
|
+
return new GzipCompressionTransform(level);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a streaming decompression transform
|
|
99
|
+
*/
|
|
100
|
+
public getDecompressionStream(): plugins.stream.Transform {
|
|
101
|
+
return new GzipDecompressionTransform();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Compress data synchronously
|
|
106
|
+
*/
|
|
107
|
+
public compressSync(data: Buffer, level?: TCompressionLevel): Buffer {
|
|
108
|
+
const options = level !== undefined ? { level } : undefined;
|
|
109
|
+
return Buffer.from(plugins.fflate.gzipSync(data, options));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Decompress data synchronously
|
|
114
|
+
*/
|
|
115
|
+
public decompressSync(data: Buffer): Buffer {
|
|
116
|
+
return Buffer.from(plugins.fflate.gunzipSync(data));
|
|
117
|
+
}
|
|
72
118
|
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Compress data asynchronously
|
|
121
|
+
* Note: Uses sync version for Deno compatibility (fflate async uses Web Workers
|
|
122
|
+
* which have issues in Deno)
|
|
123
|
+
*/
|
|
124
|
+
public async compress(data: Buffer, level?: TCompressionLevel): Promise<Buffer> {
|
|
125
|
+
// Use sync version wrapped in Promise for cross-runtime compatibility
|
|
126
|
+
return this.compressSync(data, level);
|
|
75
127
|
}
|
|
76
128
|
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Decompress data asynchronously
|
|
131
|
+
* Note: Uses sync version for Deno compatibility (fflate async uses Web Workers
|
|
132
|
+
* which have issues in Deno)
|
|
133
|
+
*/
|
|
134
|
+
public async decompress(data: Buffer): Promise<Buffer> {
|
|
135
|
+
// Use sync version wrapped in Promise for cross-runtime compatibility
|
|
136
|
+
return this.decompressSync(data);
|
|
79
137
|
}
|
|
80
138
|
}
|