Skip to content

Commit 17f3f6d

Browse files
committed
feat: JSSC Instance with event listeners
1 parent b8a4fb4 commit 17f3f6d

5 files changed

Lines changed: 201 additions & 43 deletions

File tree

bin/index.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from "fs";
22
import path from "path";
3-
import { compress, decompress, compressLargeToBase64, compressToBase64, decompressFromBase64 } from "../src/index.js";
3+
import { compress, decompress, compressLargeToBase64, compressToBase64, decompressFromBase64, JSSC } from "../src/index.js";
44
import { prefix, version, format, name__ } from "../lib/meta.js";
55
import JUSTC from "justc";
66
import { fileURLToPath } from "url";
@@ -282,6 +282,10 @@ function findEmptyDirs(dir) {
282282
return emptyDirs;
283283
}
284284

285+
const instance = windows ? new JSSC() : {
286+
compressLargeToBase64, compressToBase64, decompressFromBase64
287+
};
288+
285289
(async (inp, out, cfg) => {
286290
const inpF = await collectFiles(inp);
287291
const isFile = !str ? inpF != null : !str;
@@ -381,7 +385,10 @@ function findEmptyDirs(dir) {
381385
if (path.parse(input[0]).name != extname) extn = extname;
382386
}
383387

384-
if (windows) WinUIWait = winUIWait('Compressing "' + path.parse(inp).name + '"...');
388+
if (windows) {
389+
WinUIWait = winUIWait('Compressing "' + path.parse(inp).name + '"...');
390+
instance.events.onCompressProgress = (percentage) => {console.log(percentage, '%')}
391+
}
385392

386393
config.stringify = undefined;
387394

@@ -412,8 +419,8 @@ function findEmptyDirs(dir) {
412419
const current = p(file);
413420

414421
files.push([
415-
(await compressToBase64(current, config)).replace(/=+$/, ''),
416-
(await compressLargeToBase64(
422+
(await instance.compressToBase64(current, config)).replace(/=+$/, ''),
423+
(await instance.compressLargeToBase64(
417424
fs.readFileSync(file, { encoding: 'utf8' }),
418425
config
419426
)).replace(/=+$/, ''),
@@ -423,15 +430,15 @@ function findEmptyDirs(dir) {
423430
const dirs = [];
424431
for (const dir of findEmptyDirs(inp)) {
425432
const current = p(dir);
426-
dirs.push((await compressToBase64(current, config)).replace(/=+$/, ''));
433+
dirs.push((await instance.compressToBase64(current, config)).replace(/=+$/, ''));
427434
}
428435

429436
const startsWithDot = extn[0] == '.';
430437
fs.writeFileSync(output[0] + (
431438
addFormat ? format : ''
432439
), await toFile(
433440
isDir,
434-
extn == '' ? null : (await compressToBase64(
441+
extn == '' ? null : (await instance.compressToBase64(
435442
startsWithDot ? extn.slice(1) : extn, config
436443
)).replace(/=+$/, ''),
437444
files,
@@ -498,7 +505,7 @@ function findEmptyDirs(dir) {
498505

499506
let current;
500507
for (const [filePath, content, mtime] of files) {
501-
const delta = (await decompressFromBase64(filePath)).replaceAll("/", path.sep);
508+
const delta = (await instance.decompressFromBase64(filePath)).replaceAll("/", path.sep);
502509

503510
let fullPath;
504511
const dot = (startsWithDot ? '.' : '');
@@ -509,7 +516,7 @@ function findEmptyDirs(dir) {
509516
fullPath = path.resolve(path.dirname(current), delta);
510517
}
511518
if (!isDir) {
512-
ext = await decompressFromBase64(extn);
519+
ext = await instance.decompressFromBase64(extn);
513520
fullPath = output[0] + dot + ext;
514521
}
515522

@@ -519,7 +526,7 @@ function findEmptyDirs(dir) {
519526
if (!isRootFile) current = checkPath(fullPath);
520527

521528
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
522-
fs.writeFileSync(fullPath, await decompressFromBase64(content), { encoding: "utf8" });
529+
fs.writeFileSync(fullPath, await instance.decompressFromBase64(content), { encoding: "utf8" });
523530
try {
524531
fs.utimesSync(fullPath, mtime, mtime);
525532
} catch (err) {
@@ -529,7 +536,7 @@ function findEmptyDirs(dir) {
529536
}
530537
}
531538
for (let i = 0; i < dirs.length; i++) {
532-
const delta = (await decompressFromBase64(dirs[i])).replaceAll("/", path.sep);
539+
const delta = (await instance.decompressFromBase64(dirs[i])).replaceAll("/", path.sep);
533540

534541
let fullPath;
535542
if (typeof current === "undefined") {

src/core.js

Lines changed: 139 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import { validateCache, setCache } from './cache.js';
3030
import { compress as cAXOR, decompress as dAXOR } from './modes/axor.js';
3131
import { B64toUI8A, UI8AtoB64 } from '../lib/uint8.js';
3232

33+
let JSSCInstanceID = 0n;
34+
const JSSCInstances = {};
35+
3336
function cryptCharCode(
3437
code, get = false,
3538
repeatBefore = false, repeatAfter = false,
@@ -139,6 +142,9 @@ function readOptions(options, defaults) {
139142
if ((key == 'depth' || key.toLowerCase() == 'depthlimit' || key == 'worker' || key.toLowerCase() == 'workerlimit') && typeof value == 'number') {
140143
defaults[key.toLowerCase()] = value;
141144
continue;
145+
} if (key == 'instance' && typeof value == 'string') {
146+
defaults[key] = value;
147+
continue;
142148
}
143149
if (typeof value == 'undefined') continue;
144150
if (typeof value != 'boolean') throw new Error(prefix+'Invalid options input.');
@@ -179,7 +185,7 @@ function getModeID(code1, code2) {
179185
return code1;
180186
}
181187
}
182-
class JSSC {
188+
class JSSCDebug {
183189
constructor (com, dec, opts, m = 0, workers = false) {
184190
const headerchar = decToBin(com.charCodeAt(0), 16);
185191
const code1 = headerchar.slice(11);
@@ -300,8 +306,40 @@ export async function compress(input, options) {
300306
debug: false,
301307

302308
depth: 0,
303-
worker: 0
309+
worker: 0,
310+
311+
instance: undefined,
304312
};
313+
314+
let progressAll = 0;
315+
let progressModes = 0;
316+
let progressMisc = 0;
317+
let progressLast = 0;
318+
const candidates = [
319+
IIE,
320+
DIP,
321+
B64IE,
322+
TDCCC,
323+
TBCCC,
324+
CE,
325+
AE,
326+
FM,
327+
URL_,
328+
S,
329+
SR,
330+
EP,
331+
B64P,
332+
OE,
333+
LZS,
334+
AXOR
335+
];
336+
let done = candidates.length + 2;
337+
function onProgress() {
338+
if (typeof opts.instance != 'undefined' && progressAll != progressLast) {
339+
progressLast = progressAll;
340+
JSSCInstances[opts.instance].events['onCompressProgress'](Math.floor(progressAll / done * 100));
341+
}
342+
}
305343

306344
/* Read options */
307345
if (options) opts = readOptions(options, opts);
@@ -396,6 +434,7 @@ export async function compress(input, options) {
396434
code3 = 3;
397435
}} catch (_) {
398436
}}
437+
progressMisc++; progressAll++; onProgress();
399438

400439
if (!/\d/.test(str)) {
401440
str = repeatChars(str);
@@ -424,12 +463,19 @@ export async function compress(input, options) {
424463
}
425464

426465
const safeTry = async (fn) => {
466+
let result = null;
427467
try {
428-
return await fn();
468+
result = await fn();
429469
} catch (err) {
430470
if (opts.debug) console.warn(err);
431-
return null;
471+
result = null;
432472
}
473+
if (typeof opts.instance != 'undefined' && result) {
474+
const modeID = (new JSSCDebug(result, input, opts, 0, false)).output.mode;
475+
JSSCInstances[opts.instance].events['onCompressionMode'](modeID, input, result);
476+
progressAll++; progressModes++; onProgress();
477+
}
478+
return result;
433479
};
434480

435481
const validate = async (compressed) => {
@@ -445,26 +491,8 @@ export async function compress(input, options) {
445491
const context = {
446492
opts,
447493
str, isNum, code3, originalInput,
448-
beginId, repeatBefore
494+
beginId, repeatBefore,
449495
};
450-
const candidates = [
451-
IIE,
452-
DIP,
453-
B64IE,
454-
TDCCC,
455-
TBCCC,
456-
CE,
457-
AE,
458-
FM,
459-
URL_,
460-
S,
461-
SR,
462-
EP,
463-
B64P,
464-
OE,
465-
LZS,
466-
AXOR
467-
];
468496
async function noWorkers() {
469497
return await Promise.all(candidates.map(fn => safeTry(async () => await fn(context))));
470498
}
@@ -493,6 +521,9 @@ export async function compress(input, options) {
493521
usedWorkers = false;
494522
}
495523

524+
if (progressModes != candidates.length) progressAll = progressMisc + candidates.length;
525+
onProgress();
526+
496527
results = results.filter(r => typeof r === 'string' && r.length <= String(originalInput).length);
497528

498529
let best;
@@ -520,7 +551,11 @@ export async function compress(input, options) {
520551
if (await validateOffsetEncoding(res, best, enc[2])) best = res;
521552
}
522553

523-
if (opts.debug) return new JSSC(best, originalInput, opts, 0, usedWorkers);
554+
progressAll++; progressMisc++; onProgress();
555+
556+
if (typeof opts.instance != 'undefined') JSSCInstances[opts.instance].events['onCompressed'](input, best);
557+
558+
if (opts.debug) return new JSSCDebug(best, originalInput, opts, 0, usedWorkers);
524559

525560
return best;
526561
}
@@ -577,7 +612,9 @@ export async function decompress(str, stringify = false) {
577612
let opts = {
578613
stringify: false,
579614

580-
debug: false
615+
debug: false,
616+
617+
instance: undefined,
581618
}
582619

583620
/* Read options */
@@ -625,7 +662,8 @@ export async function decompress(str, stringify = false) {
625662
}
626663

627664
function checkOutput(out) {
628-
if (opts.debug) return new JSSC(s, out, opts, 1);
665+
if (typeof opts.instance != 'undefined') JSSCInstances[opts.instance].events['onDecompressed'](str, out);
666+
if (opts.debug) return new JSSCDebug(s, out, opts, 1);
629667
return out;
630668
}
631669
async function processOutput(out, checkOut = true) {
@@ -869,7 +907,7 @@ export async function decompress(str, stringify = false) {
869907
}
870908

871909
function noDebugMode(result) {
872-
if (result instanceof JSSC) throw new Error(prefix+'Invalid options input.');
910+
if (result instanceof JSSCDebug) throw new Error(prefix+'Invalid options input.');
873911
return result;
874912
}
875913

@@ -1330,7 +1368,8 @@ export async function S(context) {
13301368
const segOpts = {
13311369
...opts,
13321370
segmentation: false,
1333-
depth: opts.depth + 1
1371+
depth: opts.depth + 1,
1372+
instance: undefined
13341373
}
13351374
const compressed = await compress(seg, segOpts);
13361375

@@ -1467,7 +1506,8 @@ export async function OE(context) {
14671506
const res = enc[1] + await compress(enc[0], {
14681507
...opts,
14691508
offsetencoding: false,
1470-
depth: opts.depth + 1
1509+
depth: opts.depth + 1,
1510+
instance: undefined
14711511
});
14721512
if (await validateOffsetEncoding(res, originalInput, enc[2])) return res;
14731513
return null;
@@ -1509,10 +1549,79 @@ export function setWorkerURL(url) {
15091549
}
15101550
})()
15111551
)
1512-
) throw new Error(prefix+'invalid URL.');
1552+
) throw new Error(prefix+'Invalid URL.');
15131553
customWorkerURL = url;
15141554
}
15151555
export function getWorkerURL() {
15161556
if (typeof customWorkerURL == 'string' || typeof customWorkerURL == 'object') return customWorkerURL;
15171557
return workerURL;
15181558
}
1559+
1560+
/* JSSC Instance */
1561+
1562+
function JSSCFunctions(id, ...functions) {
1563+
const output = [];
1564+
for (let i = 0; i < functions.length; i++) {
1565+
output.push(async (input, options, ...args) => {
1566+
let opts = options || {};
1567+
if (typeof options == 'boolean') opts = {stringify: options};
1568+
opts.instance = id;
1569+
return await functions[i](input, opts, ...args);
1570+
});
1571+
}
1572+
return output;
1573+
}
1574+
export const JSSC = class JSSC {
1575+
#events;
1576+
constructor(cloneInstanceID) {
1577+
const ID = convertBase((JSSCInstanceID++).toString(10), 10, 64);
1578+
this.ID = ID;
1579+
const isClone = typeof cloneInstanceID == 'string' && cloneInstanceID in JSSCInstances;
1580+
const parent = JSSCInstances[cloneInstanceID];
1581+
this.#events = isClone ? parent.events : {
1582+
onCompressed: (input, output) => {},
1583+
onCompressProgress: (percentage) => {},
1584+
onCompressionMode: (modeID, input, output) => {},
1585+
onDecompressed: (input, output) => {},
1586+
};
1587+
const events = this.#events;
1588+
function set(name, func) {
1589+
if (typeof func != 'function') throw new Error(prefix+'Invalid event listener.');
1590+
events[name] = func;
1591+
JSSCInstances[ID].events = events;
1592+
}
1593+
this.events = {
1594+
get['onCompressed'] () {return events.onCompressed},
1595+
get['onCompressProgress'] () {return events.onCompressProgress},
1596+
get['onCompressionMode'] () {return events.onCompressionMode},
1597+
get['onDecompressed'] () {return events.onDecompressed},
1598+
set['onCompressed'] (func) {set('onCompressed', func)},
1599+
set['onCompressProgress'] (func) {set('onCompressProgress', func)},
1600+
set['onCompressionMode'] (func) {set('onCompressionMode', func)},
1601+
set['onDecompressed'] (func) {set('onDecompressed', func)},
1602+
};
1603+
JSSCInstances[this.ID] = this;
1604+
[
1605+
this.compress, this.decompress,
1606+
this.compressToBase64, this.decompressFromBase64,
1607+
this.compressToBase64URL, this.decompressFromBase64URL,
1608+
this.compressToUint8Array, this.decompressFromUint8Array,
1609+
this.compressLarge, this.compressLargeToBase64, this.compressLargeToBase64URL, this.compressLargeToUint8Array
1610+
] = JSSCFunctions(this.ID,
1611+
compress, decompress,
1612+
compressToBase64, decompressFromBase64,
1613+
compressToBase64URL, decompressFromBase64URL,
1614+
compressToUint8Array, decompressFromUint8Array,
1615+
compressLarge, compressLargeToBase64, compressLargeToBase64URL, compressLargeToUint8Array
1616+
);
1617+
this.clone = () => {
1618+
return new JSSC(this.ID);
1619+
};
1620+
this.delete = () => {
1621+
delete JSSCInstances[this.ID];
1622+
for (const [key] of Object.keys(this)) {
1623+
delete this[key];
1624+
}
1625+
}
1626+
}
1627+
}

0 commit comments

Comments
 (0)