diff --git a/build/locales/en/translations.json b/build/locales/en/translations.json
index b392f3ba..41ccc6c3 100644
--- a/build/locales/en/translations.json
+++ b/build/locales/en/translations.json
@@ -152,6 +152,10 @@
"confirmButtonText": "OK",
"title": "Database export",
"message": "Exported successfully"
+ },
+ "loadingWindow": {
+ "description": "Exporting DB ...",
+ "archiveSize": "archive size {{prettyArchiveSize}}"
}
},
"importDB": {
diff --git a/build/locales/ru/translations.json b/build/locales/ru/translations.json
index e96ab7c1..8c808737 100644
--- a/build/locales/ru/translations.json
+++ b/build/locales/ru/translations.json
@@ -152,6 +152,10 @@
"confirmButtonText": "OK",
"title": "Экспорт базы данных",
"message": "Экспортирована успешно"
+ },
+ "loadingWindow": {
+ "description": "Экспортирование БД ...",
+ "archiveSize": "размер архива {{prettyArchiveSize}}"
}
},
"importDB": {
diff --git a/package.json b/package.json
index 42c82aff..264e498b 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "archiver": "5.3.0",
+ "archiver": "7.0.1",
"bfx-svc-test-helper": "git+https://github.com/bitfinexcom/bfx-svc-test-helper.git",
"bittorrent-dht": "10.0.2",
"changelog-parser": "3.0.1",
diff --git a/src/archiver.js b/src/archiver.js
index b4bb460a..6a4c86b0 100644
--- a/src/archiver.js
+++ b/src/archiver.js
@@ -9,17 +9,66 @@ const {
InvalidFileNameInArchiveError
} = require('./errors')
-const zip = (
+const getTotalFilesStats = async (filePaths) => {
+ const promises = filePaths.map((filePath) => {
+ return fs.promises.stat(filePath)
+ })
+ const stats = await Promise.all(promises)
+ const size = stats.reduce((size, stat) => {
+ return Number.isFinite(stat?.size)
+ ? size + stat.size
+ : size
+ }, 0)
+
+ return {
+ size,
+ stats
+ }
+}
+
+const bytesToSize = (bytes) => {
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
+
+ if (bytes <= 0) {
+ return '0 Byte'
+ }
+
+ const i = Number.parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
+ const val = Math.round(bytes / Math.pow(1024, i), 2)
+ const size = sizes[i]
+
+ return `${val} ${size}`
+}
+
+const zip = async (
zipPath,
- filePaths = [],
- params = {}
+ filePaths,
+ params
) => {
- return new Promise((resolve, reject) => {
+ const _filePaths = Array.isArray(filePaths)
+ ? filePaths
+ : [filePaths]
+ const {
+ size,
+ stats
+ } = await getTotalFilesStats(_filePaths)
+
+ return new Promise((_resolve, _reject) => {
+ let interval = null
+ const resolve = (...args) => {
+ clearInterval(interval)
+ return _resolve(...args)
+ }
+ const reject = (err) => {
+ clearInterval(interval)
+ return _reject(err)
+ }
+
try {
- const _filePaths = Array.isArray(filePaths)
- ? filePaths
- : [filePaths]
- const { zlib } = { ...params }
+ const {
+ zlib,
+ progressHandler
+ } = params ?? {}
const _params = {
...params,
zlib: {
@@ -36,16 +85,50 @@ const zip = (
archive.on('error', reject)
archive.on('warning', reject)
+ if (typeof progressHandler === 'function') {
+ let processedBytes = 0
+
+ const asyncProgressHandler = async () => {
+ try {
+ if (
+ !Number.isFinite(size) ||
+ size === 0 ||
+ !Number.isFinite(processedBytes)
+ ) {
+ return
+ }
+
+ const progress = processedBytes / size
+ const archiveBytes = archive.pointer()
+ const prettyArchiveSize = bytesToSize(archiveBytes)
+
+ await progressHandler({
+ progress,
+ archiveBytes,
+ prettyArchiveSize
+ })
+ } catch (err) {
+ console.debug(err)
+ }
+ }
+
+ archive.on('progress', async (e) => {
+ processedBytes = e.fs.processedBytes ?? 0
+ await asyncProgressHandler()
+ })
+ interval = setInterval(asyncProgressHandler, 3000)
+ }
+
archive.pipe(output)
- _filePaths.forEach((file) => {
- const readStream = fs.createReadStream(file)
- const name = path.basename(file)
+ for (const [i, filePath] of _filePaths.entries()) {
+ const readStream = fs.createReadStream(filePath)
+ const name = path.basename(filePath)
readStream.on('error', reject)
- archive.append(readStream, { name })
- })
+ archive.append(readStream, { name, stats: stats[i] })
+ }
archive.finalize()
} catch (err) {
diff --git a/src/export-db.js b/src/export-db.js
index 8ebc5e15..5cbbfc43 100644
--- a/src/export-db.js
+++ b/src/export-db.js
@@ -10,7 +10,8 @@ const showErrorModalDialog = require('./show-error-modal-dialog')
const showMessageModalDialog = require('./show-message-modal-dialog')
const {
showLoadingWindow,
- hideLoadingWindow
+ hideLoadingWindow,
+ setLoadingDescription
} = require('./window-creators/change-loading-win-visibility-state')
const wins = require('./window-creators/windows')
const isMainWinAvailable = require('./helpers/is-main-win-available')
@@ -67,13 +68,37 @@ module.exports = ({
throw new InvalidFilePathError()
}
- await showLoadingWindow()
+ await showLoadingWindow({
+ description: i18next
+ .t('common.exportDB.loadingWindow.description')
+ })
+
+ const progressHandler = async (args) => {
+ const {
+ progress,
+ prettyArchiveSize
+ } = args ?? {}
+
+ const _description = i18next.t('common.exportDB.loadingWindow.description')
+ const _archived = i18next.t(
+ 'common.exportDB.loadingWindow.archiveSize',
+ { prettyArchiveSize }
+ )
+
+ const archived = prettyArchiveSize
+ ? `
${_archived}`
+ : ''
+ const description = `${_description}${archived}`
+
+ await setLoadingDescription({ progress, description })
+ }
+
await zip(filePath, [
dbPath,
dbShmPath,
dbWalPath,
secretKeyPath
- ])
+ ], { progressHandler })
await hideLoadingWindow()
await showMessageModalDialog(win, {
diff --git a/src/show-error-modal-dialog.js b/src/show-error-modal-dialog.js
index a2d55ac1..abc10392 100644
--- a/src/show-error-modal-dialog.js
+++ b/src/show-error-modal-dialog.js
@@ -77,7 +77,7 @@ module.exports = async (win, title = 'Error', err) => {
return _showErrorBox(win, title, message)
}
- const message = i18next.t('common.showErrorModalDialog.syncFrequencyChangingErrorMessage')
+ const message = i18next.t('common.showErrorModalDialog.unexpectedExceptionMessage')
return _showErrorBox(win, title, message)
}
diff --git a/src/window-creators/change-loading-win-visibility-state.js b/src/window-creators/change-loading-win-visibility-state.js
index 158a47ab..3962dd31 100644
--- a/src/window-creators/change-loading-win-visibility-state.js
+++ b/src/window-creators/change-loading-win-visibility-state.js
@@ -59,11 +59,12 @@ const _setParentWindow = (noParent) => {
wins.loadingWindow.setParentWindow(win)
}
-const _runProgressLoader = (opts = {}) => {
+const _runProgressLoader = (opts) => {
const {
win = wins.loadingWindow,
- isIndeterminateMode = false
- } = { ...opts }
+ isIndeterminateMode = false,
+ progress
+ } = opts ?? {}
if (
!win ||
@@ -72,6 +73,20 @@ const _runProgressLoader = (opts = {}) => {
) {
return
}
+ if (Number.isFinite(progress)) {
+ if (
+ progress >= 1 &&
+ !isIndeterminateMode
+ ) {
+ win.setProgressBar(-0.1)
+
+ return
+ }
+
+ win.setProgressBar(progress)
+
+ return
+ }
if (isIndeterminateMode) {
// Change to indeterminate mode when progress > 1
win.setProgressBar(1.1)
@@ -83,14 +98,14 @@ const _runProgressLoader = (opts = {}) => {
const duration = 3000 // ms
const interval = duration / fps // ms
const step = 1 / (duration / interval)
- let progress = 0
+ let _progress = 0
intervalMarker = setInterval(() => {
- if (progress >= 1) {
- progress = 0
+ if (_progress >= 1) {
+ _progress = 0
}
- progress += step
+ _progress += step
if (
!win ||
@@ -102,7 +117,7 @@ const _runProgressLoader = (opts = {}) => {
return
}
- win.setProgressBar(progress)
+ win.setProgressBar(_progress)
}, interval).unref()
}
@@ -123,8 +138,14 @@ const _stopProgressLoader = (
win.setProgressBar(-0.1)
}
-const _setLoadingDescription = async (win, description) => {
+const setLoadingDescription = async (params) => {
try {
+ const {
+ win = wins.loadingWindow,
+ progress,
+ description = ''
+ } = params ?? {}
+
if (
!win ||
typeof win !== 'object' ||
@@ -134,11 +155,31 @@ const _setLoadingDescription = async (win, description) => {
return
}
+ const _progressPerc = (
+ Number.isFinite(progress) &&
+ progress > 0
+ )
+ ? Math.floor(progress * 100)
+ : null
+ const progressPerc = (
+ Number.isFinite(_progressPerc) &&
+ _progressPerc > 100
+ )
+ ? 100
+ : _progressPerc
+ const descriptionChunk = description
+ ? `
${description}
` + : '' + const progressChunk = Number.isFinite(progressPerc) + ? `${progressPerc} %
` + : '' + const _description = `${progressChunk}${descriptionChunk}` + const loadingDescReadyPromise = GeneralIpcChannelHandlers .onLoadingDescriptionReady() GeneralIpcChannelHandlers - .sendLoadingDescription(win, { description }) + .sendLoadingDescription(win, { description: _description }) const loadingRes = await loadingDescReadyPromise @@ -152,6 +193,7 @@ const _setLoadingDescription = async (win, description) => { const showLoadingWindow = async (opts) => { const { + progress, description = '', isRequiredToCloseAllWins = false, isNotRunProgressLoaderRequired = false, @@ -170,14 +212,18 @@ const showLoadingWindow = async (opts) => { _setParentWindow(isRequiredToCloseAllWins || noParent) - if (!isNotRunProgressLoaderRequired) { - _runProgressLoader({ isIndeterminateMode }) + const _progress = Number.isFinite(progress) + ? Math.floor(progress * 100) / 100 + : progress + + if ( + !isNotRunProgressLoaderRequired || + Number.isFinite(progress) + ) { + _runProgressLoader({ progress: _progress, isIndeterminateMode }) } - await _setLoadingDescription( - wins.loadingWindow, - description - ) + await setLoadingDescription({ progress: _progress, description }) if (!wins.loadingWindow.isVisible()) { centerWindow(wins.loadingWindow) @@ -197,10 +243,7 @@ const hideLoadingWindow = async (opts) => { } = opts ?? {} // need to empty description - await _setLoadingDescription( - wins.loadingWindow, - '' - ) + await setLoadingDescription({ description: '' }) _stopProgressLoader() if (isRequiredToShowMainWin) { @@ -224,5 +267,6 @@ const hideLoadingWindow = async (opts) => { module.exports = { showLoadingWindow, - hideLoadingWindow + hideLoadingWindow, + setLoadingDescription }