@e-mc/file-manager 0.6.0 → 0.7.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.
Files changed (4) hide show
  1. package/LICENSE +10 -10
  2. package/index.d.ts +5 -5
  3. package/index.js +280 -14
  4. package/package.json +11 -10
package/LICENSE CHANGED
@@ -1,11 +1,11 @@
1
- Copyright 2023 An Pham
2
-
3
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
-
5
- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
-
7
- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
-
9
- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
-
1
+ Copyright 2023 An Pham
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
11
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { FileManagerConstructor } from '../types/lib';
2
- import type { ExternalAsset } from '../types/lib/asset';
3
-
4
- declare const FileManager: FileManagerConstructor<ExternalAsset>;
5
-
1
+ import type { FileManagerConstructor } from '../types/lib';
2
+ import type { ExternalAsset } from '../types/lib/asset';
3
+
4
+ declare const FileManager: FileManagerConstructor<ExternalAsset>;
5
+
6
6
  export = FileManager;
package/index.js CHANGED
@@ -4,6 +4,7 @@ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  const path = require("path");
6
6
  const fs = require("fs");
7
+ const pm = require("picomatch");
7
8
  const lib_v4_1 = require("../module/lib-v4");
8
9
  const util_1 = require("../request/util");
9
10
  const asset_1 = require("../document/asset");
@@ -126,6 +127,87 @@ function resetAssets() {
126
127
  this.reset();
127
128
  FileManager.sanitizeAssets(this.assets);
128
129
  }
130
+ function recurseDir(output, subDirs, options) {
131
+ const { ignore, sortBy, recursive } = options;
132
+ const items = fs.readdirSync(path.join(...subDirs), { withFileTypes: true })
133
+ .filter(item => item.name)
134
+ .sort((a, b) => {
135
+ if (sortBy > 0) {
136
+ if (a.isDirectory() && !b.isDirectory()) {
137
+ return sortBy & 1 /* READDIR_SORT.DIRECTORY */ ? -1 : 1;
138
+ }
139
+ if (!a.isDirectory() && b.isDirectory()) {
140
+ return sortBy & 1 /* READDIR_SORT.DIRECTORY */ ? 1 : -1;
141
+ }
142
+ }
143
+ return (a.name < b.name ? -1 : 1) * (2 /* READDIR_SORT.DESCENDING */ & 2 ? -1 : 1);
144
+ });
145
+ for (const item of items) {
146
+ if (!item.isDirectory()) {
147
+ const pathname = path.join(item.path, item.name);
148
+ if (!ignore.includes(pathname)) {
149
+ output.push(pathname);
150
+ }
151
+ }
152
+ else if (recursive) {
153
+ recurseDir(output, subDirs.concat(item.name), options);
154
+ }
155
+ }
156
+ return output;
157
+ }
158
+ function checkHash(data, fromBuffer, options) {
159
+ if (options) {
160
+ let algorithm, digest, value;
161
+ if ((0, types_1.isObject)(options)) {
162
+ ({ algorithm, digest, value } = options);
163
+ }
164
+ else {
165
+ value = options;
166
+ }
167
+ if ((0, types_1.isString)(value)) {
168
+ if (!fromBuffer) {
169
+ try {
170
+ data = fs.readFileSync(data);
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ return value === core_1.Host.asHash(data, { algorithm, digest });
177
+ }
178
+ }
179
+ return true;
180
+ }
181
+ function validatePaths(values, patterns, include, dot) {
182
+ const items = patterns.map(value => !value.startsWith('*') && /[\\/]/.test(value) ? [core_1.Permission.toPosix(path.resolve(value)), { nocase: PLATFORM_WIN32, dot }] : [value.replace(/\\/g, '/'), { matchBase: true, nocase: PLATFORM_WIN32, dot }]);
183
+ return values.filter(value => {
184
+ for (const [pattern, options] of items) {
185
+ if (pm.isMatch(core_1.Permission.toPosix(value), pattern, options)) {
186
+ return include;
187
+ }
188
+ }
189
+ return !include;
190
+ });
191
+ }
192
+ function filterPaths(values, include, exclude, dot) {
193
+ if (include) {
194
+ if ((0, types_1.isString)(include)) {
195
+ include = [include];
196
+ }
197
+ if ((0, types_1.isArray)(include)) {
198
+ return validatePaths(values, include, true, dot);
199
+ }
200
+ }
201
+ if (exclude) {
202
+ if ((0, types_1.isString)(exclude)) {
203
+ exclude = [exclude];
204
+ }
205
+ if ((0, types_1.isArray)(exclude)) {
206
+ return validatePaths(values, exclude, false, dot);
207
+ }
208
+ }
209
+ return values;
210
+ }
129
211
  function abortedHost() {
130
212
  if (this.finalizeState === 6 /* FINALIZE_STATE.ABORTED */) {
131
213
  this.restarting = false;
@@ -195,6 +277,8 @@ function collectErrors() {
195
277
  function rejectModule(err, type, hint) {
196
278
  this.writeFail(["Handled rejection" /* ERR_MESSAGE.HANDLED_REJECTION */, this.moduleName + (hint ? ': ' + hint : '')], err, type);
197
279
  }
280
+ const checksumFile = (algorithm) => "checksum" /* HASH_OUTPUT.FILENAME */ + '.' + ((0, types_1.isString)(algorithm) ? algorithm.toLowerCase() : "sha256" /* HASH_OUTPUT.ALGORITHM */);
281
+ const checksumError = (algorithm) => new Error("Invalid parameters" /* ERR_MESSAGE.PARAMETERS */ + ` (${algorithm || "sha256" /* HASH_OUTPUT.ALGORITHM */})`);
198
282
  const isFunction = (value) => typeof value === 'function';
199
283
  const ignoreAsset = (item, exists) => item.invalid || (0, types_1.hasBit)(item.flags, 1 /* ASSET_FLAG.IGNORE */ | (!exists ? 128 /* ASSET_FLAG.EXISTS */ : 0));
200
284
  class HttpDiskCache {
@@ -557,6 +641,146 @@ class FileManager extends core_1.Host {
557
641
  });
558
642
  return assets;
559
643
  }
644
+ static async writeChecksum(root, to, options) {
645
+ if ((0, types_1.isObject)(to)) {
646
+ options = to;
647
+ to = undefined;
648
+ }
649
+ else {
650
+ options || (options = {});
651
+ }
652
+ const { algorithm, digest, sortBy = 0, recursive = false, ignore = [], include, exclude, verbose = false, joinRoot } = options;
653
+ to || (to = checksumFile(algorithm));
654
+ let result = [];
655
+ try {
656
+ const filename = path.basename(to);
657
+ to = joinRoot ? path.join(root, to) : path.resolve(to);
658
+ recurseDir(result, [root], { ignore: [...ignore, to], sortBy, recursive });
659
+ const output = [];
660
+ for (const pathname of result = filterPaths(result, include, exclude, options.dot)) {
661
+ if (recursive === 1 && path.basename(pathname) === filename) {
662
+ continue;
663
+ }
664
+ const current = await this.readHash(pathname, { algorithm, digest }) + ' ' + (path.sep === '\\' ? pathname.replace(/\\/g, '/') : pathname).substring(root.length).replace(/^\//, '');
665
+ if (verbose) {
666
+ process.stdout.write(current + '\n');
667
+ }
668
+ output.push(current);
669
+ }
670
+ if (output.length) {
671
+ fs.writeFileSync(to, output.join('\n'), 'utf-8');
672
+ options.outPath = to;
673
+ }
674
+ else {
675
+ if (this.isPath(to)) {
676
+ fs.unlinkSync(to);
677
+ }
678
+ if (options.throwsEmpty) {
679
+ throw checksumError(algorithm);
680
+ }
681
+ }
682
+ }
683
+ catch (err) {
684
+ if (options.throwsEmpty) {
685
+ throw err;
686
+ }
687
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, root], err, 32 /* LOG_TYPE.FILE */);
688
+ return null;
689
+ }
690
+ return result;
691
+ }
692
+ static async verifyChecksum(root, from, options) {
693
+ if ((0, types_1.isObject)(from)) {
694
+ options = from;
695
+ from = undefined;
696
+ }
697
+ else {
698
+ options || (options = {});
699
+ }
700
+ const { algorithm, digest, sortBy = 0, recursive = false, ignore = [], include, exclude, verbose = true, joinRoot } = options;
701
+ const parent = recursive === 1 && typeof verbose !== 'number';
702
+ from || (from = checksumFile(algorithm));
703
+ let fail = [], missing = [];
704
+ try {
705
+ const filename = path.basename(from);
706
+ const files = [];
707
+ from = joinRoot ? path.join(root, filename) : path.resolve(from);
708
+ recurseDir(files, [root], { ignore: [...ignore, from], sortBy, recursive });
709
+ const items = fs.readFileSync(from, 'utf-8').split('\n').map(item => {
710
+ const index = item.indexOf(' ');
711
+ return [item.substring(0, index), path.join(root, item.substring(index + 1))];
712
+ });
713
+ const checked = include || exclude ? filterPaths(items.map(item => item[1]), include, exclude, options.dot) : null;
714
+ let valid;
715
+ for (const [previous, pathname] of items) {
716
+ if (checked !== null && !checked.includes(pathname) || recursive === 1 && path.basename(pathname) === filename) {
717
+ continue;
718
+ }
719
+ if (files.includes(pathname)) {
720
+ const hash = await this.readHash(pathname, { algorithm, digest });
721
+ if (hash !== previous) {
722
+ fail.push(pathname);
723
+ }
724
+ else if (verbose) {
725
+ process.stdout.write("+" /* SUMDIR_STATUS.PASS */ + ' ' + pathname + '\n');
726
+ }
727
+ valid = true;
728
+ }
729
+ else if (!ignore.includes(pathname)) {
730
+ missing.push(pathname);
731
+ }
732
+ }
733
+ if (parent) {
734
+ const nested = files.filter(value => path.basename(value) === filename);
735
+ if (nested.length) {
736
+ const current = items.map(item => item[1]);
737
+ const tasks = nested.map(pathname => this.verifyChecksum(path.dirname(pathname), filename, { ...options, ignore: current, joinRoot: true, verbose: (verbose ? 1 : 0) }));
738
+ await Promise.all(tasks).then(group => {
739
+ for (const item of group) {
740
+ if (item) {
741
+ const [f, m] = item;
742
+ if (f.length) {
743
+ fail.push(...f);
744
+ }
745
+ if (m.length) {
746
+ missing.push(...m);
747
+ }
748
+ valid = true;
749
+ }
750
+ }
751
+ });
752
+ }
753
+ }
754
+ if (valid) {
755
+ options.outPath = from;
756
+ }
757
+ else if (options.throwsEmpty) {
758
+ throw checksumError(algorithm);
759
+ }
760
+ }
761
+ catch (err) {
762
+ if (options.throwsEmpty) {
763
+ throw err;
764
+ }
765
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, root], err, 32 /* LOG_TYPE.FILE */);
766
+ return null;
767
+ }
768
+ if (parent) {
769
+ if (fail.length) {
770
+ fail = Array.from(new Set(fail));
771
+ }
772
+ if (missing.length) {
773
+ missing = Array.from(new Set(missing));
774
+ }
775
+ }
776
+ if (verbose === true) {
777
+ const max = Math.max(...fail.concat(missing).map(item => item.length));
778
+ const writeLog = (items, symbol) => items.forEach((value, index) => process.stdout.write(`${symbol} ${value.padEnd(max)} (${(index + 1).toString()})\n`));
779
+ writeLog(fail, "-" /* SUMDIR_STATUS.FAIL */);
780
+ writeLog(missing, "?" /* SUMDIR_STATUS.MISSING */);
781
+ }
782
+ return [fail, missing];
783
+ }
560
784
  static createFileThread(host, file) {
561
785
  return new FileThread(host, file, 0);
562
786
  }
@@ -2582,6 +2806,10 @@ class FileManager extends core_1.Host {
2582
2806
  return true;
2583
2807
  };
2584
2808
  const fileReceived = (item, localUri, err, fetched, binary) => {
2809
+ if (item.checksum && !err && !checkHash(item.buffer || localUri, !!item.buffer, item.checksum)) {
2810
+ err = (0, types_1.errorValue)("Checksum did not match" /* ERR_MESSAGE.FAILED_CHECKSUM */, item.uri);
2811
+ this.filesToRemove.add(localUri);
2812
+ }
2585
2813
  if (err) {
2586
2814
  item.invalid = true;
2587
2815
  this.completeAsyncTask(err, localUri);
@@ -2690,7 +2918,7 @@ class FileManager extends core_1.Host {
2690
2918
  if (!cached) {
2691
2919
  setBuffer(item);
2692
2920
  }
2693
- if (core_1.Host.isPath(localUri)) {
2921
+ if (core_1.Host.isPath(localUri) && (!item.checksumOutput || checkHash(localUri, false, item.checksumOutput))) {
2694
2922
  item.flags |= 128 /* ASSET_FLAG.EXISTS */;
2695
2923
  if (!etag || item.fetchType !== 1 /* FETCH_TYPE.HTTP */) {
2696
2924
  if (!(0, types_1.isEmpty)(bundleId) && bundleIndex === 0) {
@@ -2708,13 +2936,15 @@ class FileManager extends core_1.Host {
2708
2936
  else if (cached) {
2709
2937
  try {
2710
2938
  const buffer = cached[1];
2711
- fs.writeFileSync(localUri, buffer);
2712
- if (item.willChange && Buffer.isBuffer(buffer)) {
2713
- item.buffer = buffer;
2939
+ if (!item.checksum || checkHash(buffer, true, item.checksum)) {
2940
+ fs.writeFileSync(localUri, buffer);
2941
+ if (item.willChange && Buffer.isBuffer(buffer)) {
2942
+ item.buffer = buffer;
2943
+ }
2944
+ this.performAsyncTask();
2945
+ fileReceived(item, localUri, null, true);
2946
+ continue;
2714
2947
  }
2715
- this.performAsyncTask();
2716
- fileReceived(item, localUri, null, true);
2717
- continue;
2718
2948
  }
2719
2949
  catch {
2720
2950
  }
@@ -3157,13 +3387,6 @@ class FileManager extends core_1.Host {
3157
3387
  return cloud ? cloud_1.default.finalize.call(this, cloud).catch(err => rejectModule.call(cloud, err, 64 /* LOG_TYPE.CLOUD */)) : Promise.resolve();
3158
3388
  }
3159
3389
  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
3390
  if (this.emptyDir.size) {
3168
3391
  for (const value of Array.from(this.emptyDir).reverse()) {
3169
3392
  try {
@@ -3237,11 +3460,54 @@ class FileManager extends core_1.Host {
3237
3460
  return Promise.reject((0, types_1.createAbortError)());
3238
3461
  }
3239
3462
  removeFiles();
3463
+ for (const item of this.assets) {
3464
+ if (item.checksumOutput && !ignoreAsset(item)) {
3465
+ const localUri = item.localUri;
3466
+ if (!checkHash(localUri, false, item.checksumOutput)) {
3467
+ item.invalid = true;
3468
+ filesToRemove.add(localUri);
3469
+ 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 });
3470
+ }
3471
+ }
3472
+ }
3473
+ removeFiles();
3240
3474
  await this.finalizeCloud().catch(err => rejectModule.call(this, err, 64 /* LOG_TYPE.CLOUD */));
3241
3475
  if (this.aborted) {
3242
3476
  return Promise.reject((0, types_1.createAbortError)());
3243
3477
  }
3244
3478
  removeFiles();
3479
+ if (this.Compress) {
3480
+ const tasks = [];
3481
+ this.assets.forEach(item => item.compress && !ignoreAsset(item, true) && tasks.push(this.compressFile(item, false)));
3482
+ if (tasks.length) {
3483
+ await Promise.allSettled(tasks);
3484
+ }
3485
+ }
3486
+ let checksum = this.config.checksum;
3487
+ if (checksum) {
3488
+ if (typeof checksum === 'boolean' || checksum === 1) {
3489
+ checksum = { recursive: checksum };
3490
+ }
3491
+ else if ((0, types_1.isString)(checksum)) {
3492
+ const items = checksum.split('.');
3493
+ checksum = items.length > 1 ? { algorithm: items[items.length - 1], filename: checksum } : { algorithm: checksum };
3494
+ }
3495
+ if ((0, types_1.isPlainObject)(checksum)) {
3496
+ const baseDirectory = this.baseDirectory;
3497
+ checksum.joinRoot = true;
3498
+ checksum.throwsEmpty = true;
3499
+ const sumTime = LOG_TIMEELAPSED ? process.hrtime() : 0;
3500
+ try {
3501
+ const files = (await FileManager.writeChecksum(baseDirectory, checksum.filename, checksum));
3502
+ if (sumTime) {
3503
+ this.writeTimeElapsed(checksum.algorithm || "sha256" /* HASH_OUTPUT.ALGORITHM */, [baseDirectory, files.length + (files.length === 1 ? ' file' : ' files')], sumTime, { ...core_1.Host.LOG_STYLE_WARN });
3504
+ }
3505
+ }
3506
+ catch (err) {
3507
+ this.writeFail(["Unable to read directory" /* ERR_MESSAGE.READ_DIRECTORY */, path.basename(baseDirectory)], err, { type: 32 /* LOG_TYPE.FILE */, startTime });
3508
+ }
3509
+ }
3510
+ }
3245
3511
  await this.finalizeCleanup().catch(err => rejectModule.call(this, err, 1 /* LOG_TYPE.SYSTEM */));
3246
3512
  removeFiles();
3247
3513
  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.1",
4
4
  "description": "FileManager constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -20,14 +20,15 @@
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.1",
24
+ "@e-mc/compress": "0.7.1",
25
+ "@e-mc/core": "0.7.1",
26
+ "@e-mc/document": "0.7.1",
27
+ "@e-mc/image": "0.7.1",
28
+ "@e-mc/request": "0.7.1",
29
+ "@e-mc/task": "0.7.1",
30
+ "@e-mc/types": "0.7.1",
31
+ "@e-mc/watch": "0.7.1",
32
+ "picomatch": "^3.0.1"
32
33
  }
33
34
  }