|
1 | 1 | const fs = require('fs').promises |
2 | 2 | const p = require('path') |
3 | | -const { EventEmitter } = require('events') |
4 | 3 |
|
5 | 4 | const cliProgress = require('cli-progress') |
6 | | -const mirrorFolder = require('mirror-folder') |
7 | | -const low = require('last-one-wins') |
8 | | -const streamx = require('streamx') |
9 | | -const pump = require('pump') |
10 | 5 | const { flags } = require('@oclif/command') |
11 | 6 |
|
12 | 7 | const DaemonCommand = require('../../lib/cli') |
13 | 8 | const { HyperdriveClient } = require('../..') |
14 | 9 |
|
15 | | -const KEY_FILE_PATH = '.hyperdrive-export-key' |
16 | | - |
17 | | -class DriveWatcher extends EventEmitter { |
18 | | - constructor (client, drive, opts = {}) { |
19 | | - super() |
20 | | - this.client = client |
21 | | - this.drive = drive |
22 | | - this.recursive = !!opts.recursive |
23 | | - this.drivesByPath = new Map([[ '/', drive ]]) |
24 | | - this.versionsByPath = new Map() |
25 | | - this.unwatchesByPath = new Map() |
26 | | - this.watchers = [] |
27 | | - this.timer = null |
28 | | - this.emittingStats = false |
29 | | - } |
30 | | - |
31 | | - _createDiffer (path, drive) { |
32 | | - // Last-one-wins in case the watch is triggered many times in quick succession. |
33 | | - const self = this |
34 | | - return low(onupdate) |
35 | | - |
36 | | - async function onupdate (_, cb) { |
37 | | - const lastVersion = self.versionsByPath.get(path) |
38 | | - try { |
39 | | - const diffStream = await drive.createDiffStream(lastVersion) |
40 | | - const currentVersion = await drive.version() |
41 | | - self.versionsByPath.set(path, currentVersion) |
42 | | - return pump(diffStream, new streamx.Transform({ |
43 | | - transform: (data, cb) => { |
44 | | - for (const watcher of self.watchers) { |
45 | | - watcher(p.join(path, data.name)) |
46 | | - } |
47 | | - return cb(null) |
48 | | - } |
49 | | - }), err => { |
50 | | - if (err) return cb(err) |
51 | | - return cb(null) |
52 | | - }) |
53 | | - } catch (err) { |
54 | | - return cb(err) |
55 | | - } |
56 | | - } |
57 | | - } |
58 | | - |
59 | | - async _emitStats () { |
60 | | - if (this.emittingStats) return |
61 | | - this.emittingStats = true |
62 | | - var total = 0 |
63 | | - var downloaded = 0 |
64 | | - var peers = 0 |
65 | | - for (const [path, drive] of this.drivesByPath) { |
66 | | - const driveStats = await drive.stats() |
67 | | - for (const { path, content } of driveStats.stats) { |
68 | | - if (path !== '/' || !content) continue |
69 | | - downloaded += content.downloadedBlocks |
70 | | - total += content.totalBlocks |
71 | | - peers = content.peers |
72 | | - } |
73 | | - } |
74 | | - this.emit('stats', { total, downloaded, peers }) |
75 | | - this.emittingStats = false |
76 | | - } |
77 | | - |
78 | | - async start () { |
79 | | - // TODO: Handle dynamic (un)mounting. |
80 | | - this.versionsByPath.set('/', await this.drive.version()) |
81 | | - this.unwatchesByPath.set('/', this.drive.watch('/', this._createDiffer('/', this.drive))) |
82 | | - const allMounts = await this.drive.mounts({ memory: false, recursive: this.recursive }) |
83 | | - for (const { path, opts } of allMounts) { |
84 | | - if (path === '/') continue |
85 | | - const childDrive = await this.client.drive.get({ key: opts.key }) |
86 | | - this.drivesByPath.set(path, childDrive) |
87 | | - this.versionsByPath.set(path, opts.version) |
88 | | - this.unwatchesByPath.set(path, childDrive.watch('/', this._createDiffer(path, childDrive))) |
89 | | - } |
90 | | - this.timer = setInterval(this._emitStats.bind(this), 1000) |
91 | | - } |
92 | | - |
93 | | - watch (_, onwatch) { |
94 | | - // The watch path is ignored for drives. |
95 | | - this.watchers.push(onwatch) |
96 | | - return () => { |
97 | | - this.watchers.splice(this.watchers.indexOf(onwatch), 1) |
98 | | - } |
99 | | - } |
100 | | - |
101 | | - async close () { |
102 | | - for (const [path, unwatch] of this.unwatchesByPath) { |
103 | | - await unwatch() |
104 | | - } |
105 | | - if (this.timer) { |
106 | | - clearInterval(this.timer) |
107 | | - this.timer = null |
108 | | - } |
109 | | - } |
110 | | -} |
| 10 | +const { exportKeyFilePath: KEY_FILE_PATH } = require('../../lib/constants') |
111 | 11 |
|
112 | 12 | class ExportCommand extends DaemonCommand { |
113 | 13 | static usage = 'export [key] [dir]' |
@@ -163,29 +63,23 @@ class ExportCommand extends DaemonCommand { |
163 | 63 | const drive = await this.client.drive.get({ key }) |
164 | 64 | if (!loadedKey) await saveKeyToFile() |
165 | 65 |
|
166 | | - const driveWatcher = new DriveWatcher(this.client, drive, { |
167 | | - recursive: flags.recursive |
168 | | - }) |
169 | | - await driveWatcher.start() |
170 | | - |
171 | 66 | const progress = new cliProgress.SingleBar({ |
172 | 67 | format: `Exporting | {bar} | {percentage}% | {value}/{total} Content Blocks | {peers} Peers` |
173 | 68 | }) |
174 | 69 | console.log(`Exporting ${key.toString('hex')} into ${dir} (Ctrl+c to exit)...`) |
175 | 70 | console.log() |
176 | 71 | progress.start(1, 0) |
177 | | - driveWatcher.on('stats', stats => { |
178 | | - progress.setTotal(stats.total) |
179 | | - progress.update(stats.downloaded, { peers: stats.peers }) |
180 | | - }) |
181 | 72 |
|
182 | 73 | process.on('SIGINT', cleanup) |
183 | 74 | process.on('SIGTERM', cleanup) |
184 | 75 |
|
185 | | - const remoteMirror = mirrorFolder({ fs: drive, name: '/' }, dir, { |
186 | | - watch: driveWatcher.watch.bind(driveWatcher), |
187 | | - keepExisting: true, |
188 | | - ensureParents: true |
| 76 | + const driveWatcher = this.client.drive.export(drive, dir, {recursive: flags.recursive}) |
| 77 | + |
| 78 | + await driveWatcher.start() |
| 79 | + |
| 80 | + driveWatcher.on('stats', stats => { |
| 81 | + progress.setTotal(stats.total) |
| 82 | + progress.update(stats.downloaded, { peers: stats.peers }) |
189 | 83 | }) |
190 | 84 |
|
191 | 85 | async function cleanup () { |
|
0 commit comments