@e-mc/file-manager 0.6.0 → 0.7.0

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.
Files changed (2) hide show
  1. package/index.js +268 -14
  2. package/package.json +10 -10
package/index.js CHANGED
@@ -126,6 +126,76 @@ function resetAssets() {
126
126
  this.reset();
127
127
  FileManager.sanitizeAssets(this.assets);
128
128
  }
129
+ function recurseDir(output, subDirs, options) {
130
+ const { ignore, sortBy, recursive } = options;
131
+ const items = fs.readdirSync(path.join(...subDirs), { withFileTypes: true })
132
+ .filter(item => item.name)
133
+ .sort((a, b) => {
134
+ if (sortBy > 0) {
135
+ if (a.isDirectory() && !b.isDirectory()) {
136
+ return sortBy & 1 /* READDIR_SORT.DIRECTORY */ ? -1 : 1;
137
+ }
138
+ if (!a.isDirectory() && b.isDirectory()) {
139
+ return sortBy & 1 /* READDIR_SORT.DIRECTORY */ ? 1 : -1;
140
+ }
141
+ }
142
+ return (a.name < b.name ? -1 : 1) * (2 /* READDIR_SORT.DESCENDING */ & 2 ? -1 : 1);
143
+ });
144
+ for (const item of items) {
145
+ if (!item.isDirectory()) {
146
+ const pathname = path.join(item.path, item.name);
147
+ if (!ignore.includes(pathname)) {
148
+ output.push(pathname);
149
+ }
150
+ }
151
+ else if (recursive) {
152
+ recurseDir(output, subDirs.concat(item.name), options);
153
+ }
154
+ }
155
+ return output;
156
+ }
157
+ function checkHash(data, fromBuffer, options) {
158
+ if (options) {
159
+ let algorithm, digest, value;
160
+ if ((0, types_1.isObject)(options)) {
161
+ ({ algorithm, digest, value } = options);
162
+ }
163
+ else {
164
+ value = options;
165
+ }
166
+ if ((0, types_1.isString)(value)) {
167
+ if (!fromBuffer) {
168
+ try {
169
+ data = fs.readFileSync(data);
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ }
175
+ return value === core_1.Host.asHash(data, { algorithm, digest });
176
+ }
177
+ }
178
+ return true;
179
+ }
180
+ function filterPaths(values, include, exclude) {
181
+ if (include) {
182
+ if ((0, types_1.isString)(include)) {
183
+ include = [include];
184
+ }
185
+ if ((0, types_1.isArray)(include)) {
186
+ return values.filter(value => core_1.Permission.match(value, include));
187
+ }
188
+ }
189
+ if (exclude) {
190
+ if ((0, types_1.isString)(exclude)) {
191
+ exclude = [exclude];
192
+ }
193
+ if ((0, types_1.isArray)(exclude)) {
194
+ return values.filter(value => !core_1.Permission.match(value, exclude));
195
+ }
196
+ }
197
+ return values;
198
+ }
129
199
  function abortedHost() {
130
200
  if (this.finalizeState === 6 /* FINALIZE_STATE.ABORTED */) {
131
201
  this.restarting = false;
@@ -195,6 +265,8 @@ function collectErrors() {
195
265
  function rejectModule(err, type, hint) {
196
266
  this.writeFail(["Handled rejection" /* ERR_MESSAGE.HANDLED_REJECTION */, this.moduleName + (hint ? ': ' + hint : '')], err, type);
197
267
  }
268
+ const checksumFile = (algorithm) => "checksum" /* HASH_OUTPUT.FILENAME */ + '.' + ((0, types_1.isString)(algorithm) ? algorithm.toLowerCase() : "sha256" /* HASH_OUTPUT.ALGORITHM */);
269
+ const checksumError = (algorithm) => new Error("Invalid parameters" /* ERR_MESSAGE.PARAMETERS */ + ` (${algorithm || "sha256" /* HASH_OUTPUT.ALGORITHM */})`);
198
270
  const isFunction = (value) => typeof value === 'function';
199
271
  const ignoreAsset = (item, exists) => item.invalid || (0, types_1.hasBit)(item.flags, 1 /* ASSET_FLAG.IGNORE */ | (!exists ? 128 /* ASSET_FLAG.EXISTS */ : 0));
200
272
  class HttpDiskCache {
@@ -557,6 +629,146 @@ class FileManager extends core_1.Host {
557
629
  });
558
630
  return assets;
559
631
  }
632
+ static async writeChecksum(root, to, options) {
633
+ if ((0, types_1.isObject)(to)) {
634
+ options = to;
635
+ to = undefined;
636
+ }
637
+ else {
638
+ options || (options = {});
639
+ }
640
+ const { algorithm, digest, sortBy = 0, recursive = false, ignore = [], include, exclude, verbose = false, joinRoot } = options;
641
+ to || (to = checksumFile(algorithm));
642
+ let result = [];
643
+ try {
644
+ const filename = path.basename(to);
645
+ to = joinRoot ? path.join(root, to) : path.resolve(to);
646
+ recurseDir(result, [root], { ignore: [...ignore, to], sortBy, recursive });
647
+ const output = [];
648
+ for (const pathname of result = filterPaths(result, include, exclude)) {
649
+ if (recursive === 1 && path.basename(pathname) === filename) {
650
+ continue;
651
+ }
652
+ const current = await this.readHash(pathname, { algorithm, digest }) + ' ' + (path.sep === '\\' ? pathname.replace(/\\/g, '/') : pathname).substring(root.length).replace(/^\//, '');
653
+ if (verbose) {
654
+ process.stdout.write(current + '\n');
655
+ }
656
+ output.push(current);
657
+ }
658
+ if (output.length) {
659
+ fs.writeFileSync(to, output.join('\n'), 'utf-8');
660
+ options.outPath = to;
661
+ }
662
+ else {
663
+ if (this.isPath(to)) {
664
+ fs.unlinkSync(to);
665
+ }
666
+ if (options.throwsEmpty) {
667
+ throw checksumError(algorithm);
668
+ }
669
+ }
670
+ }
671
+ catch (err) {
672
+ if (options.throwsEmpty) {
673
+ throw err;
674
+ }
675
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, root], err, 32 /* LOG_TYPE.FILE */);
676
+ return null;
677
+ }
678
+ return result;
679
+ }
680
+ static async verifyChecksum(root, from, options) {
681
+ if ((0, types_1.isObject)(from)) {
682
+ options = from;
683
+ from = undefined;
684
+ }
685
+ else {
686
+ options || (options = {});
687
+ }
688
+ const { algorithm, digest, sortBy = 0, recursive = false, ignore = [], include, exclude, verbose = true, joinRoot } = options;
689
+ const parent = recursive === 1 && typeof verbose !== 'number';
690
+ from || (from = checksumFile(algorithm));
691
+ let fail = [], missing = [];
692
+ try {
693
+ const filename = path.basename(from);
694
+ const files = [];
695
+ from = joinRoot ? path.join(root, filename) : path.resolve(from);
696
+ recurseDir(files, [root], { ignore: [...ignore, from], sortBy, recursive });
697
+ const items = fs.readFileSync(from, 'utf-8').split('\n').map(item => {
698
+ const index = item.indexOf(' ');
699
+ return [item.substring(0, index), path.join(root, item.substring(index + 1))];
700
+ });
701
+ const checked = include || exclude ? filterPaths(items.map(item => item[1]), include, exclude) : null;
702
+ let valid;
703
+ for (const [previous, pathname] of items) {
704
+ if (checked !== null && !checked.includes(pathname) || recursive === 1 && path.basename(pathname) === filename) {
705
+ continue;
706
+ }
707
+ if (files.includes(pathname)) {
708
+ const hash = await this.readHash(pathname, { algorithm, digest });
709
+ if (hash !== previous) {
710
+ fail.push(pathname);
711
+ }
712
+ else if (verbose) {
713
+ process.stdout.write("+" /* SUMDIR_STATUS.PASS */ + ' ' + pathname + '\n');
714
+ }
715
+ valid = true;
716
+ }
717
+ else if (!ignore.includes(pathname)) {
718
+ missing.push(pathname);
719
+ }
720
+ }
721
+ if (parent) {
722
+ const nested = files.filter(value => path.basename(value) === filename);
723
+ if (nested.length) {
724
+ const current = items.map(item => item[1]);
725
+ const tasks = nested.map(pathname => this.verifyChecksum(path.dirname(pathname), filename, { ...options, ignore: current, joinRoot: true, verbose: (verbose ? 1 : 0) }));
726
+ await Promise.all(tasks).then(group => {
727
+ for (const item of group) {
728
+ if (item) {
729
+ const [f, m] = item;
730
+ if (f.length) {
731
+ fail.push(...f);
732
+ }
733
+ if (m.length) {
734
+ missing.push(...m);
735
+ }
736
+ valid = true;
737
+ }
738
+ }
739
+ });
740
+ }
741
+ }
742
+ if (valid) {
743
+ options.outPath = from;
744
+ }
745
+ else if (options.throwsEmpty) {
746
+ throw checksumError(algorithm);
747
+ }
748
+ }
749
+ catch (err) {
750
+ if (options.throwsEmpty) {
751
+ throw err;
752
+ }
753
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, root], err, 32 /* LOG_TYPE.FILE */);
754
+ return null;
755
+ }
756
+ if (parent) {
757
+ if (fail.length) {
758
+ fail = Array.from(new Set(fail));
759
+ }
760
+ if (missing.length) {
761
+ missing = Array.from(new Set(missing));
762
+ }
763
+ }
764
+ if (verbose === true) {
765
+ const max = Math.max(...fail.concat(missing).map(item => item.length));
766
+ const writeLog = (items, symbol) => items.forEach((value, index) => process.stdout.write(`${symbol} ${value.padEnd(max)} (${(index + 1).toString()})\n`));
767
+ writeLog(fail, "-" /* SUMDIR_STATUS.FAIL */);
768
+ writeLog(missing, "?" /* SUMDIR_STATUS.MISSING */);
769
+ }
770
+ return [fail, missing];
771
+ }
560
772
  static createFileThread(host, file) {
561
773
  return new FileThread(host, file, 0);
562
774
  }
@@ -2582,6 +2794,10 @@ class FileManager extends core_1.Host {
2582
2794
  return true;
2583
2795
  };
2584
2796
  const fileReceived = (item, localUri, err, fetched, binary) => {
2797
+ if (item.checksum && !err && !checkHash(item.buffer || localUri, !!item.buffer, item.checksum)) {
2798
+ err = (0, types_1.errorValue)("Checksum did not match" /* ERR_MESSAGE.FAILED_CHECKSUM */, item.uri);
2799
+ this.filesToRemove.add(localUri);
2800
+ }
2585
2801
  if (err) {
2586
2802
  item.invalid = true;
2587
2803
  this.completeAsyncTask(err, localUri);
@@ -2690,7 +2906,7 @@ class FileManager extends core_1.Host {
2690
2906
  if (!cached) {
2691
2907
  setBuffer(item);
2692
2908
  }
2693
- if (core_1.Host.isPath(localUri)) {
2909
+ if (core_1.Host.isPath(localUri) && (!item.checksumOutput || checkHash(localUri, false, item.checksumOutput))) {
2694
2910
  item.flags |= 128 /* ASSET_FLAG.EXISTS */;
2695
2911
  if (!etag || item.fetchType !== 1 /* FETCH_TYPE.HTTP */) {
2696
2912
  if (!(0, types_1.isEmpty)(bundleId) && bundleIndex === 0) {
@@ -2708,13 +2924,15 @@ class FileManager extends core_1.Host {
2708
2924
  else if (cached) {
2709
2925
  try {
2710
2926
  const buffer = cached[1];
2711
- fs.writeFileSync(localUri, buffer);
2712
- if (item.willChange && Buffer.isBuffer(buffer)) {
2713
- item.buffer = buffer;
2927
+ if (!item.checksum || checkHash(buffer, true, item.checksum)) {
2928
+ fs.writeFileSync(localUri, buffer);
2929
+ if (item.willChange && Buffer.isBuffer(buffer)) {
2930
+ item.buffer = buffer;
2931
+ }
2932
+ this.performAsyncTask();
2933
+ fileReceived(item, localUri, null, true);
2934
+ continue;
2714
2935
  }
2715
- this.performAsyncTask();
2716
- fileReceived(item, localUri, null, true);
2717
- continue;
2718
2936
  }
2719
2937
  catch {
2720
2938
  }
@@ -3157,13 +3375,6 @@ class FileManager extends core_1.Host {
3157
3375
  return cloud ? cloud_1.default.finalize.call(this, cloud).catch(err => rejectModule.call(cloud, err, 64 /* LOG_TYPE.CLOUD */)) : Promise.resolve();
3158
3376
  }
3159
3377
  async finalizeCleanup() {
3160
- if (this.Compress) {
3161
- const tasks = [];
3162
- this.assets.forEach(item => item.compress && !ignoreAsset(item, true) && tasks.push(this.compressFile(item, false)));
3163
- if (tasks.length) {
3164
- await Promise.allSettled(tasks);
3165
- }
3166
- }
3167
3378
  if (this.emptyDir.size) {
3168
3379
  for (const value of Array.from(this.emptyDir).reverse()) {
3169
3380
  try {
@@ -3237,11 +3448,54 @@ class FileManager extends core_1.Host {
3237
3448
  return Promise.reject((0, types_1.createAbortError)());
3238
3449
  }
3239
3450
  removeFiles();
3451
+ for (const item of this.assets) {
3452
+ if (item.checksumOutput && !ignoreAsset(item)) {
3453
+ const localUri = item.localUri;
3454
+ if (!checkHash(localUri, false, item.checksumOutput)) {
3455
+ item.invalid = true;
3456
+ filesToRemove.add(localUri);
3457
+ this.writeFail(["Invalid checksum" /* ERR_MESSAGE.CHECKSUM */, path.basename(localUri)], (0, types_1.errorValue)("Checksum did not match" /* ERR_MESSAGE.FAILED_CHECKSUM */, localUri), { type: 32 /* LOG_TYPE.FILE */, startTime });
3458
+ }
3459
+ }
3460
+ }
3461
+ removeFiles();
3240
3462
  await this.finalizeCloud().catch(err => rejectModule.call(this, err, 64 /* LOG_TYPE.CLOUD */));
3241
3463
  if (this.aborted) {
3242
3464
  return Promise.reject((0, types_1.createAbortError)());
3243
3465
  }
3244
3466
  removeFiles();
3467
+ if (this.Compress) {
3468
+ const tasks = [];
3469
+ this.assets.forEach(item => item.compress && !ignoreAsset(item, true) && tasks.push(this.compressFile(item, false)));
3470
+ if (tasks.length) {
3471
+ await Promise.allSettled(tasks);
3472
+ }
3473
+ }
3474
+ let checksum = this.config.checksum;
3475
+ if (checksum) {
3476
+ if (typeof checksum === 'boolean' || checksum === 1) {
3477
+ checksum = { recursive: checksum };
3478
+ }
3479
+ else if ((0, types_1.isString)(checksum)) {
3480
+ const items = checksum.split('.');
3481
+ checksum = items.length > 1 ? { algorithm: items[items.length - 1], filename: checksum } : { algorithm: checksum };
3482
+ }
3483
+ if ((0, types_1.isPlainObject)(checksum)) {
3484
+ const baseDirectory = this.baseDirectory;
3485
+ checksum.joinRoot = true;
3486
+ checksum.throwsEmpty = true;
3487
+ const sumTime = LOG_TIMEELAPSED ? process.hrtime() : 0;
3488
+ try {
3489
+ const files = (await FileManager.writeChecksum(baseDirectory, checksum.filename, checksum));
3490
+ if (sumTime) {
3491
+ this.writeTimeElapsed(checksum.algorithm || "sha256" /* HASH_OUTPUT.ALGORITHM */, [baseDirectory, files.length + (files.length === 1 ? ' file' : ' files')], sumTime, { ...core_1.Host.LOG_STYLE_WARN });
3492
+ }
3493
+ }
3494
+ catch (err) {
3495
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, path.basename(baseDirectory)], err, { type: 32 /* LOG_TYPE.FILE */, startTime });
3496
+ }
3497
+ }
3498
+ }
3245
3499
  await this.finalizeCleanup().catch(err => rejectModule.call(this, err, 1 /* LOG_TYPE.SYSTEM */));
3246
3500
  removeFiles();
3247
3501
  if (LOG_TIMEELAPSED) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/file-manager",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "FileManager constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -20,14 +20,14 @@
20
20
  "license": "BSD 3-Clause",
21
21
  "homepage": "https://github.com/anpham6/e-mc#readme",
22
22
  "dependencies": {
23
- "@e-mc/cloud": "0.6.0",
24
- "@e-mc/compress": "0.6.0",
25
- "@e-mc/core": "0.6.0",
26
- "@e-mc/document": "0.6.0",
27
- "@e-mc/image": "0.6.0",
28
- "@e-mc/request": "0.6.0",
29
- "@e-mc/task": "0.6.0",
30
- "@e-mc/types": "0.6.0",
31
- "@e-mc/watch": "0.6.0"
23
+ "@e-mc/cloud": "0.7.0",
24
+ "@e-mc/compress": "0.7.0",
25
+ "@e-mc/core": "0.7.0",
26
+ "@e-mc/document": "0.7.0",
27
+ "@e-mc/image": "0.7.0",
28
+ "@e-mc/request": "0.7.0",
29
+ "@e-mc/task": "0.7.0",
30
+ "@e-mc/types": "0.7.0",
31
+ "@e-mc/watch": "0.7.0"
32
32
  }
33
33
  }