@abtnode/core 1.8.65-beta-5405baf2 → 1.8.65-beta-bfcc12ce
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/lib/api/team.js +1 -0
- package/lib/blocklet/downloader/blocklet-downloader.js +33 -12
- package/lib/blocklet/manager/disk.js +293 -217
- package/lib/blocklet/manager/helper/install-from-backup.js +13 -4
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +4 -0
- package/lib/blocklet/storage/backup/blocklets.js +23 -29
- package/lib/blocklet/storage/backup/data.js +2 -2
- package/lib/blocklet/storage/backup/logs.js +3 -2
- package/lib/blocklet/storage/backup/spaces.js +3 -14
- package/lib/blocklet/storage/restore/blocklet-extras.js +8 -3
- package/lib/blocklet/storage/restore/blocklets.js +29 -11
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +6 -1
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/router/helper.js +82 -9
- package/lib/router/index.js +8 -1
- package/lib/states/blocklet.js +5 -2
- package/lib/util/blocklet.js +10 -7
- package/lib/validators/router.js +7 -1
- package/package.json +27 -25
|
@@ -11,6 +11,14 @@ const logger = require('@abtnode/logger')('@abtnode/core:install-from-backup');
|
|
|
11
11
|
|
|
12
12
|
const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../util/blocklet');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {{
|
|
17
|
+
* manager: import('../disk'),
|
|
18
|
+
* states: import('../../../states/index')
|
|
19
|
+
* }} param0
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
14
22
|
module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states, manager } = {}) => {
|
|
15
23
|
// TODO: support more url schema feature (http, did-spaces)
|
|
16
24
|
if (!url.startsWith('file://')) {
|
|
@@ -24,11 +32,7 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
|
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
// parse data from source dir
|
|
27
|
-
|
|
28
35
|
const srcBundleDirs = await getAppDirs(path.join(dir, 'blocklets'));
|
|
29
|
-
if (!srcBundleDirs.length) {
|
|
30
|
-
throw new Error(`bundle dirs does not found in ${dir}`);
|
|
31
|
-
}
|
|
32
36
|
|
|
33
37
|
const srcDataDir = path.join(dir, 'data');
|
|
34
38
|
|
|
@@ -61,6 +65,7 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
|
|
|
61
65
|
|
|
62
66
|
// FIXME: meta.did and meta.name should be dynamic created when installing multiple blocklet is supported
|
|
63
67
|
const existState = await states.blocklet.hasBlocklet(did);
|
|
68
|
+
|
|
64
69
|
if (existState) {
|
|
65
70
|
logger.error('blocklet is already exist', { did });
|
|
66
71
|
throw new Error('blocklet is already exist');
|
|
@@ -71,6 +76,8 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
|
|
|
71
76
|
const skConfig = extra.configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
|
|
72
77
|
if (skConfig) {
|
|
73
78
|
skConfig.value = blockletSecretKey;
|
|
79
|
+
skConfig.secure = true;
|
|
80
|
+
skConfig.shared = false;
|
|
74
81
|
} else {
|
|
75
82
|
extra.configs.push({
|
|
76
83
|
key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
|
|
@@ -131,6 +138,8 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
|
|
|
131
138
|
logger.info(`bundle is ${moveDir ? 'moved' : 'copied'} successfully`, { installDir });
|
|
132
139
|
})
|
|
133
140
|
);
|
|
141
|
+
// 从 store 下载 blocklet
|
|
142
|
+
await manager.blockletDownloader.download(state, { skipCheckIntegrity: true });
|
|
134
143
|
|
|
135
144
|
// FIXME same as copy extra
|
|
136
145
|
const dataDir = path.join(manager.dataDirs.data, appName);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DIR_NAME = 'rollback-cache';
|
|
5
|
+
const security = require('@abtnode/util/lib/security');
|
|
6
|
+
|
|
7
|
+
class RollbackCache {
|
|
8
|
+
constructor({ dir, dek }) {
|
|
9
|
+
this.dir = dir;
|
|
10
|
+
this.dek = dek;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async backup({ did, action, oldBlocklet }) {
|
|
14
|
+
const file = this._getFile(did);
|
|
15
|
+
const data = this.dek ? security.encrypt(JSON.stringify(oldBlocklet), did, this.dek) : oldBlocklet;
|
|
16
|
+
await fs.outputJSON(file, { action, oldBlocklet: data });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async remove({ did }) {
|
|
20
|
+
const file = this._getFile(did);
|
|
21
|
+
await fs.remove(file);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async restore({ did }) {
|
|
25
|
+
const file = this._getFile(did);
|
|
26
|
+
if (fs.existsSync(file)) {
|
|
27
|
+
const data = await fs.readJSON(file);
|
|
28
|
+
if (data?.oldBlocklet && this.dek) {
|
|
29
|
+
data.oldBlocklet = JSON.parse(security.decrypt(data.oldBlocklet, did, this.dek));
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_getFile(did) {
|
|
37
|
+
return path.join(this.dir, DIR_NAME, `${did}.json`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = RollbackCache;
|
|
@@ -75,6 +75,10 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
75
75
|
* @memberof BlockletExtrasBackup
|
|
76
76
|
*/
|
|
77
77
|
useBlockletEncryptConfigs(configs) {
|
|
78
|
+
if (isEmpty(configs)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
// 准备加解密所需的参数
|
|
79
83
|
// @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
|
|
80
84
|
const dek = readFileSync(join(this.serverDataDir, '.sock'));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const
|
|
3
|
-
const archiver = require('archiver');
|
|
1
|
+
const { join } = require('path');
|
|
2
|
+
const validUrl = require('valid-url');
|
|
4
3
|
const { BaseBackup } = require('./base');
|
|
4
|
+
const { dirToZip } = require('../utils/zip');
|
|
5
|
+
const { compareAndMove } = require('../utils/hash');
|
|
5
6
|
|
|
6
7
|
class BlockletsBackup extends BaseBackup {
|
|
7
8
|
/**
|
|
@@ -13,21 +14,25 @@ class BlockletsBackup extends BaseBackup {
|
|
|
13
14
|
const blockletMetas = this.getBlockletMetas(this.blocklet);
|
|
14
15
|
const serverBlockletsDir = join(this.serverDataDir, 'blocklets');
|
|
15
16
|
|
|
17
|
+
const blockletMetasFromLocal = blockletMetas.filter(
|
|
18
|
+
(b) => !validUrl.isHttpUri(b.tarball) && !validUrl.isHttpsUri(b.tarball)
|
|
19
|
+
);
|
|
20
|
+
|
|
16
21
|
const dirs = [];
|
|
17
|
-
for (const blockletMeta of
|
|
22
|
+
for (const blockletMeta of blockletMetasFromLocal) {
|
|
18
23
|
const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
|
|
19
|
-
const
|
|
20
|
-
dirs.push({ sourceDir,
|
|
24
|
+
const zipPath = join(this.blockletBackupDir, 'blocklets', blockletMeta.name, `${blockletMeta.version}.zip`);
|
|
25
|
+
dirs.push({ sourceDir, zipPath });
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
await this.dirsToZip(dirs
|
|
28
|
+
await this.dirsToZip(dirs);
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
32
|
*
|
|
28
33
|
*
|
|
29
34
|
* @param {import('@abtnode/client').BlockletState} blocklet
|
|
30
|
-
* @returns {Array<{name: string, version: string}>}
|
|
35
|
+
* @returns {Array<{name: string, version: string, tarball: string}>}
|
|
31
36
|
* @memberof BlockletsBackup
|
|
32
37
|
*/
|
|
33
38
|
getBlockletMetas(blocklet) {
|
|
@@ -39,6 +44,7 @@ class BlockletsBackup extends BaseBackup {
|
|
|
39
44
|
metas.push({
|
|
40
45
|
name: blocklet.meta.bundleName,
|
|
41
46
|
version: blocklet.meta.version,
|
|
47
|
+
tarball: blocklet?.meta?.dist?.tarball,
|
|
42
48
|
});
|
|
43
49
|
|
|
44
50
|
for (const child of blocklet.children) {
|
|
@@ -49,31 +55,19 @@ class BlockletsBackup extends BaseBackup {
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
52
|
-
* @param {Array<{sourceDir: string,
|
|
58
|
+
* @param {Array<{sourceDir: string, zipPath: string}>} dirs: /some/folder/to/compress
|
|
53
59
|
* @param {String} zipPath: /path/to/created.zip
|
|
54
60
|
* @returns {Promise}
|
|
55
61
|
* @memberof BlockletsBackup
|
|
56
62
|
*/
|
|
57
|
-
async dirsToZip(dirs
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
archive.on('error', (err) => reject(err));
|
|
68
|
-
stream.on('close', () => resolve());
|
|
69
|
-
|
|
70
|
-
for (const dir of dirs) {
|
|
71
|
-
archive.directory(dir.sourceDir, dir.destDir);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
archive.pipe(stream);
|
|
75
|
-
archive.finalize();
|
|
76
|
-
});
|
|
63
|
+
async dirsToZip(dirs) {
|
|
64
|
+
await Promise.all(
|
|
65
|
+
dirs.map(async (dir) => {
|
|
66
|
+
const tempZipPath = `${dir.zipPath}.bak`;
|
|
67
|
+
await dirToZip(dir.sourceDir, tempZipPath);
|
|
68
|
+
await compareAndMove(dir.zipPath, tempZipPath);
|
|
69
|
+
})
|
|
70
|
+
);
|
|
77
71
|
}
|
|
78
72
|
}
|
|
79
73
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { copy } = require('fs-extra');
|
|
2
2
|
const { join } = require('path');
|
|
3
3
|
const { BaseBackup } = require('./base');
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ class DataBackup extends BaseBackup {
|
|
|
12
12
|
const blockletDataDir = join(this.serverDataDir, 'data', this.blocklet.meta.name);
|
|
13
13
|
const blockletBackupDataDir = join(this.blockletBackupDir, 'data');
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
await copy(blockletDataDir, blockletBackupDataDir, { overwrite: true });
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const { ensureDirSync,
|
|
1
|
+
const { ensureDirSync, copy, statSync } = require('fs-extra');
|
|
2
2
|
const { join } = require('path');
|
|
3
3
|
const { BaseBackup } = require('./base');
|
|
4
4
|
|
|
5
|
+
// note: 可以不需要备份
|
|
5
6
|
class LogsBackup extends BaseBackup {
|
|
6
7
|
async export() {
|
|
7
8
|
const sourceLogsDir = join(this.serverDataDir, 'logs', this.blocklet.meta.name);
|
|
@@ -9,7 +10,7 @@ class LogsBackup extends BaseBackup {
|
|
|
9
10
|
|
|
10
11
|
const targetLogsDir = join(this.blockletBackupDir, 'logs');
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
await copy(sourceLogsDir, targetLogsDir, {
|
|
13
14
|
overwrite: true,
|
|
14
15
|
filter: (src) => {
|
|
15
16
|
return !statSync(src).isSymbolicLink();
|
|
@@ -8,7 +8,7 @@ const { isValid } = require('@arcblock/did');
|
|
|
8
8
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
9
9
|
const { getBlockletInfo } = require('@blocklet/meta');
|
|
10
10
|
const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
|
|
11
|
-
const { ensureDirSync
|
|
11
|
+
const { ensureDirSync } = require('fs-extra');
|
|
12
12
|
const { isEmpty } = require('lodash');
|
|
13
13
|
const { join } = require('path');
|
|
14
14
|
const states = require('../../../states');
|
|
@@ -17,7 +17,6 @@ const { BlockletBackup } = require('./blocklet');
|
|
|
17
17
|
const { BlockletExtrasBackup } = require('./blocklet-extras');
|
|
18
18
|
const { BlockletsBackup } = require('./blocklets');
|
|
19
19
|
const { DataBackup } = require('./data');
|
|
20
|
-
const { LogsBackup } = require('./logs');
|
|
21
20
|
const { RoutingRuleBackup } = require('./routing-rule');
|
|
22
21
|
|
|
23
22
|
class SpacesBackup {
|
|
@@ -78,7 +77,6 @@ class SpacesBackup {
|
|
|
78
77
|
this.input = input;
|
|
79
78
|
this.storages = [
|
|
80
79
|
new AuditLogBackup(this.input),
|
|
81
|
-
new LogsBackup(this.input),
|
|
82
80
|
new BlockletBackup(this.input),
|
|
83
81
|
new BlockletsBackup(this.input),
|
|
84
82
|
new BlockletExtrasBackup(this.input),
|
|
@@ -107,7 +105,6 @@ class SpacesBackup {
|
|
|
107
105
|
await this.initialize();
|
|
108
106
|
await this.export();
|
|
109
107
|
await this.syncToSpaces();
|
|
110
|
-
await this.destroy();
|
|
111
108
|
}
|
|
112
109
|
|
|
113
110
|
async initialize() {
|
|
@@ -121,9 +118,6 @@ class SpacesBackup {
|
|
|
121
118
|
this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
|
|
122
119
|
|
|
123
120
|
this.blockletBackupDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/backup', this.blocklet.meta.did);
|
|
124
|
-
if (existsSync(this.blockletBackupDir)) {
|
|
125
|
-
rmdirSync(this.blockletBackupDir, { recursive: true });
|
|
126
|
-
}
|
|
127
121
|
ensureDirSync(this.blockletBackupDir);
|
|
128
122
|
|
|
129
123
|
this.spacesEndpoint = this.blocklet.environments.find(
|
|
@@ -158,7 +152,8 @@ class SpacesBackup {
|
|
|
158
152
|
source: join(this.blockletBackupDir, '/'),
|
|
159
153
|
target: join('.did-objects', this.blocklet.appDid),
|
|
160
154
|
debug: true,
|
|
161
|
-
|
|
155
|
+
concurrency: 64,
|
|
156
|
+
retryCount: 100,
|
|
162
157
|
filter: (object) => {
|
|
163
158
|
return object.name !== '.DS_Store';
|
|
164
159
|
},
|
|
@@ -176,12 +171,6 @@ class SpacesBackup {
|
|
|
176
171
|
const { wallet } = getBlockletInfo(blockletInfo, nodeInfo.sk);
|
|
177
172
|
return wallet;
|
|
178
173
|
}
|
|
179
|
-
|
|
180
|
-
async destroy() {
|
|
181
|
-
if (existsSync(this.blockletBackupDir)) {
|
|
182
|
-
removeSync(this.blockletBackupDir);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
174
|
}
|
|
186
175
|
|
|
187
176
|
module.exports = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
|
|
2
|
-
const { cloneDeep } = require('lodash');
|
|
2
|
+
const { cloneDeep, isArray } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const security = require('@abtnode/util/lib/security');
|
|
5
5
|
const { BaseRestore } = require('./base');
|
|
@@ -61,6 +61,13 @@ class BlockletExtrasRestore extends BaseRestore {
|
|
|
61
61
|
* @memberof BlockletExtrasRestore
|
|
62
62
|
*/
|
|
63
63
|
useBlockletDecryptConfigs(configs) {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log('debug', configs, !configs || !isArray(configs));
|
|
66
|
+
|
|
67
|
+
if (!configs || !isArray(configs)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
for (const config of configs) {
|
|
65
72
|
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
66
73
|
if (config.secure) {
|
|
@@ -76,8 +83,6 @@ class BlockletExtrasRestore extends BaseRestore {
|
|
|
76
83
|
config.value = decryptByBlocklet;
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
|
-
|
|
80
|
-
return configs;
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -1,23 +1,41 @@
|
|
|
1
|
-
const { existsSync,
|
|
1
|
+
const { existsSync, remove } = require('fs-extra');
|
|
2
2
|
const { join } = require('path');
|
|
3
|
-
const
|
|
3
|
+
const fg = require('fast-glob');
|
|
4
4
|
const { BaseRestore } = require('./base');
|
|
5
|
+
const { zipToDir } = require('../utils/zip');
|
|
5
6
|
|
|
6
7
|
class BlockletsRestore extends BaseRestore {
|
|
7
|
-
filename = 'blocklets
|
|
8
|
+
filename = 'blocklets';
|
|
8
9
|
|
|
9
10
|
async import() {
|
|
10
|
-
const
|
|
11
|
+
const blockletsDir = join(this.blockletRestoreDir, this.filename);
|
|
11
12
|
|
|
12
|
-
if (!existsSync(
|
|
13
|
-
throw new Error(`
|
|
13
|
+
if (!existsSync(blockletsDir)) {
|
|
14
|
+
throw new Error(`dir not found: ${blockletsDir}`);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const paths = await fg('**/*.zip', {
|
|
18
|
+
cwd: blockletsDir,
|
|
19
|
+
onlyFiles: true,
|
|
20
|
+
absolute: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @type {{source: string, target: string}}
|
|
25
|
+
*/
|
|
26
|
+
const zipConfigs = paths.map((path) => {
|
|
27
|
+
return {
|
|
28
|
+
source: path,
|
|
29
|
+
target: path.replace(/.zip$/, ''),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await Promise.all(
|
|
34
|
+
zipConfigs.map(async (zipConfig) => {
|
|
35
|
+
await zipToDir(zipConfig.source, zipConfig.target);
|
|
36
|
+
await remove(zipConfig.source);
|
|
37
|
+
})
|
|
38
|
+
);
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { removeSync, existsSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { BaseRestore } = require('./base');
|
|
4
|
+
const { zipToDir } = require('../utils/zip');
|
|
5
|
+
|
|
6
|
+
class LogsRestore extends BaseRestore {
|
|
7
|
+
filename = 'logs.zip';
|
|
8
|
+
|
|
9
|
+
async import() {
|
|
10
|
+
const blockletZipPath = join(this.blockletRestoreDir, this.filename);
|
|
11
|
+
|
|
12
|
+
if (!existsSync(blockletZipPath)) {
|
|
13
|
+
throw new Error(`file not found: ${blockletZipPath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await zipToDir(blockletZipPath, join(this.blockletRestoreDir, 'logs'));
|
|
17
|
+
removeSync(blockletZipPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { LogsRestore };
|
|
@@ -74,6 +74,10 @@ class SpacesRestore {
|
|
|
74
74
|
this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
|
|
75
75
|
this.blockletWallet = await this.getBlockletWallet();
|
|
76
76
|
|
|
77
|
+
if (!this.input.endpoint.includes(this.blockletWallet.address)) {
|
|
78
|
+
throw new Error(`endpoint and blocklet.appDid(${this.blockletWallet.address}) do not match`);
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
|
|
78
82
|
if (existsSync(this.blockletRestoreDir)) {
|
|
79
83
|
rmdirSync(this.blockletRestoreDir, { recursive: true });
|
|
@@ -113,7 +117,8 @@ class SpacesRestore {
|
|
|
113
117
|
source: join('.did-objects', this.blockletWallet.address, '/'),
|
|
114
118
|
target: this.blockletRestoreDir,
|
|
115
119
|
debug: true,
|
|
116
|
-
|
|
120
|
+
concurrency: 64,
|
|
121
|
+
retryCount: 100,
|
|
117
122
|
})
|
|
118
123
|
);
|
|
119
124
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { removeSync, existsSync, move, createReadStream } = require('fs-extra');
|
|
2
|
+
const hasha = require('hasha');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
*
|
|
7
|
+
* @param {NodeJS.ReadableStream} stream1
|
|
8
|
+
* @param {NodeJS.ReadableStream} stream2
|
|
9
|
+
* @return {Promise<boolean>}
|
|
10
|
+
*/
|
|
11
|
+
async function compareHash(stream1, stream2) {
|
|
12
|
+
const hash1 = await hasha.fromStream(stream1, {
|
|
13
|
+
algorithm: 'md5',
|
|
14
|
+
});
|
|
15
|
+
const hash2 = await hasha.fromStream(stream2, {
|
|
16
|
+
algorithm: 'md5',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return hash1 === hash2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @description 比对新文件和旧文件的hash,旧文件不存在或者hash不同时,使用新文件替换,hash相同,删除新文件
|
|
25
|
+
* @param {string} oldFilePath
|
|
26
|
+
* @param {string} newFilePath
|
|
27
|
+
* @returns {Promise<boolean>}
|
|
28
|
+
*/
|
|
29
|
+
async function compareAndMove(oldFilePath, newFilePath) {
|
|
30
|
+
if (!existsSync(oldFilePath)) {
|
|
31
|
+
await move(newFilePath, oldFilePath, { overwrite: true });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!existsSync(newFilePath)) {
|
|
36
|
+
throw new Error(`newFilePath(${newFilePath}) not found`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isSame = await compareHash(createReadStream(oldFilePath), createReadStream(newFilePath));
|
|
40
|
+
if (isSame) {
|
|
41
|
+
removeSync(newFilePath);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await move(newFilePath, oldFilePath, { overwrite: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
compareHash,
|
|
50
|
+
compareAndMove,
|
|
51
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const archiver = require('archiver');
|
|
2
|
+
const { ensureDirSync, existsSync, removeSync, createWriteStream } = require('fs-extra');
|
|
3
|
+
const { dirname } = require('path');
|
|
4
|
+
const StreamZip = require('node-stream-zip');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* @param {string} source ~/abc/ 通常是一个文件夹
|
|
10
|
+
* @param {string} target abc.zip 压缩包的名称
|
|
11
|
+
* @return {*}
|
|
12
|
+
*/
|
|
13
|
+
function dirToZip(source, target) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
ensureDirSync(dirname(target));
|
|
16
|
+
|
|
17
|
+
if (existsSync(target)) {
|
|
18
|
+
removeSync(target);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const output = createWriteStream(target);
|
|
22
|
+
const archive = archiver('zip', { level: 9 });
|
|
23
|
+
archive.on('error', (err) => reject(err));
|
|
24
|
+
output.on('close', () => resolve());
|
|
25
|
+
|
|
26
|
+
archive.directory(source, false);
|
|
27
|
+
|
|
28
|
+
archive.pipe(output);
|
|
29
|
+
archive.finalize();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function zipToDir(source, target) {
|
|
34
|
+
// eslint-disable-next-line new-cap
|
|
35
|
+
const zip = new StreamZip.async({ file: source });
|
|
36
|
+
await zip.extract(null, target);
|
|
37
|
+
await zip.close();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
dirToZip,
|
|
42
|
+
zipToDir,
|
|
43
|
+
};
|