From 678316547fadeb4dcdc4dcfbc74cc146209f4cf3 Mon Sep 17 00:00:00 2001 From: troyeguo <13820674+troyeguo@users.noreply.github.com> Date: Sun, 18 Jul 2021 23:23:31 +0800 Subject: [PATCH] fix bug Former-commit-id: ffb6df3902b5f945c4572f11c5dce04a3b9bc4b1 --- src/assets/locales/cn/translation.json | 2 +- src/assets/locales/en/translation.json | 4 +- src/assets/locales/ru/translation.json | 4 +- src/assets/locales/tw/translation.json | 4 +- .../dialogs/backupDialog/component.tsx | 88 ++++++-- .../dialogs/settingDialog/component.tsx | 46 ++-- src/components/emptyCover/emptyCover.css | 1 + src/components/importLocal/component.tsx | 3 +- src/components/textToSpeech/component.tsx | 2 - src/containers/header/component.tsx | 120 +++++----- src/containers/lists/bookList/component.tsx | 9 +- src/containers/sidebar/component.tsx | 1 + src/utils/syncUtils/backupUtil.tsx | 124 ++-------- src/utils/syncUtils/common.tsx | 213 +++++++++++++++--- src/utils/syncUtils/dropbox.tsx | 124 +++++----- src/utils/syncUtils/restoreUtil.tsx | 116 ++-------- src/utils/syncUtils/webdav.tsx | 181 ++++++++------- 17 files changed, 529 insertions(+), 513 deletions(-) diff --git a/src/assets/locales/cn/translation.json b/src/assets/locales/cn/translation.json index 368416e9..2270b30e 100644 --- a/src/assets/locales/cn/translation.json +++ b/src/assets/locales/cn/translation.json @@ -218,7 +218,7 @@ "Line Height": "行间距", "Tips": "提示", "How sync works": "同步是如何实现的?", - "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "同步功能需要配合第三方同步盘实现,在不同电脑上把数据存储位置修改为同一个同步文件夹,手动点击同步按钮后,Koodo会把同步文件夹中的数据更新到软件中,从而实现同步。", + "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "同步功能需要配合第三方同步盘实现,在不同电脑上把数据存储位置修改为同一个网盘文件夹,点击同步按钮后,Koodo会自动进行数据的双向同步。目前该功能还处在测试阶段,请谨慎使用。", "Please choose an empty folder": "请选择空文件夹", "Data change detected, whether to update?": "检测到数据变化,是否更新", "Empty Library": "图书库为空", diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json index 7892370a..321a3071 100644 --- a/src/assets/locales/en/translation.json +++ b/src/assets/locales/en/translation.json @@ -231,7 +231,7 @@ "Tips": "Tips", "Brightness": "Brightness", "How sync works": "How sync works", - "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.", + "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp. This function is still in beta, use with caution.", "Line Height": "Line Height", "Please choose an empty folder": "Please choose an empty folder", "Data change detected, whether to update?": "Data change detected, whether to update?", @@ -294,4 +294,4 @@ "Our Website": "Our Website", "What's New": "What's New", "What's been fixed": "What's been fixed" -} \ No newline at end of file +} diff --git a/src/assets/locales/ru/translation.json b/src/assets/locales/ru/translation.json index 57efe8ec..e498e84d 100644 --- a/src/assets/locales/ru/translation.json +++ b/src/assets/locales/ru/translation.json @@ -269,7 +269,7 @@ "Invert color": "Invert color", "Auto Update relys on Github Release for package hosting, if your internet doesn't have stable connection to Github, we highly recommand you to turn off this option": "Auto Update relys on Github Release for package hosting, if your internet doesn't have stable connection to Github, we highly recommand you to turn off this option", "How sync works": "How sync works", - "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.", + "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp. This function is still in beta, use with caution.", "Purple": "Purple", "Please choose an empty folder": "Please choose an empty folder", "Data change detected, whether to update?": "Data change detected, whether to update?", @@ -294,4 +294,4 @@ "What's been fixed": "What's been fixed", "Voice": "Voice", "Speed": "Speed" -} \ No newline at end of file +} diff --git a/src/assets/locales/tw/translation.json b/src/assets/locales/tw/translation.json index 657027d0..c1bf7802 100644 --- a/src/assets/locales/tw/translation.json +++ b/src/assets/locales/tw/translation.json @@ -281,7 +281,7 @@ "System Font": "系統字體", "Tips": "提示", "How sync works": "同步是如何實現的?", - "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "同步功能需要配合第三方同步盤實現,在不同電腦上把數據存儲位置修改為同一個同步文件夾,手動點擊同步按鈕後,Koodo會把同步文件夾中的數據更新到軟件中,從而實現同步。", + "You need to manually change the storage location to the same sync folder on different computers. When you click the sync button, Koodo Reader will automatically upload or download the data from this folder according the timestamp.": "同步功能需要配合第三方同步盤實現,在不同電腦上把數據存儲位置修改為同一個網盤文件夾,點擊同步按鈕後,Koodo會自動進行數據的雙向同步。目前該功能還處在測試階段,請謹慎使用。", "Please choose an empty folder": "請選擇空文件夾", "Data change detected, whether to update?": "檢測到數據變化,是否更新", "File size": "文件大小", @@ -292,4 +292,4 @@ "Hide navigation button": "不显示翻页按钮", "More": "显示笔记出处", "Sync data from storage": "从存储位置同步数据" -} \ No newline at end of file +} diff --git a/src/components/dialogs/backupDialog/component.tsx b/src/components/dialogs/backupDialog/component.tsx index f64f87b4..923959db 100644 --- a/src/components/dialogs/backupDialog/component.tsx +++ b/src/components/dialogs/backupDialog/component.tsx @@ -2,8 +2,8 @@ import React from "react"; import "./backupDialog.css"; import { driveList } from "../../../constants/driveList"; -import BackupUtil from "../../../utils/syncUtils/backupUtil"; -import RestoreUtil from "../../../utils/syncUtils/restoreUtil"; +import { backup } from "../../../utils/syncUtils/backupUtil"; +import { restore } from "../../../utils/syncUtils/restoreUtil"; import { Trans } from "react-i18next"; import DropboxUtil from "../../../utils/syncUtils/dropbox"; import WebdavUtil from "../../../utils/syncUtils/webdav"; @@ -12,7 +12,7 @@ import TokenDialog from "../tokenDialog"; import OtherUtil from "../../../utils/otherUtil"; import Lottie from "react-lottie"; import animationSuccess from "../../../assets/lotties/success.json"; - +import FileSaver from "file-saver"; const successOptions = { loop: false, autoplay: true, @@ -42,26 +42,40 @@ class BackupDialog extends React.Component< this.props.handleLoadingDialog(false); this.showMessage("Sync Successfully"); }; - handleRestoreToLocal = (event: any) => { + handleRestoreToLocal = async (event: any) => { event.preventDefault(); - RestoreUtil.restore(event.target.files[0], this.handleFinish); + let result = await restore(event.target.files[0]); + if (result) { + this.handleFinish(); + } }; showMessage = (message: string) => { this.props.handleMessage(message); this.props.handleMessageBox(true); }; handleDrive = (index: number) => { - this.setState({ currentDrive: index }, () => { + let year = new Date().getFullYear(), + month = new Date().getMonth() + 1, + day = new Date().getDate(); + this.setState({ currentDrive: index }, async () => { switch (index) { case 0: - BackupUtil.backup( + let blob: Blob | boolean = await backup( this.props.books, this.props.notes, this.props.bookmarks, - this.handleFinish, - 0, - this.showMessage + false ); + if (!blob) { + this.showMessage("Backup Failed"); + } + FileSaver.saveAs( + blob as Blob, + `${year}-${month <= 9 ? "0" + month : month}-${ + day <= 9 ? "0" + day : day + }.zip` + ); + this.handleFinish(); break; case 1: if (!OtherUtil.getReaderConfig("dropbox_token")) { @@ -73,18 +87,32 @@ class BackupDialog extends React.Component< this.showMessage("Uploading"); this.props.handleLoadingDialog(true); - BackupUtil.backup( + let blob: Blob | boolean = await backup( this.props.books, this.props.notes, this.props.bookmarks, - this.handleFinish, - 1, - this.showMessage + false ); + if (!blob) { + this.showMessage("Backup Failed"); + this.props.handleLoadingDialog(false); + } + let result = await DropboxUtil.UploadFile(blob); + if (result) { + this.handleFinish(); + } else { + this.showMessage("Upload failed, check your connection"); + } } else { this.props.handleLoadingDialog(true); this.showMessage("Downloading"); - DropboxUtil.DownloadFile(this.handleFinish, this.showMessage); + let result = await DropboxUtil.DownloadFile(); + if (result) { + this.handleFinish(); + } else { + this.showMessage("Download failed,network problem or no backup"); + this.props.handleLoadingDialog(false); + } } break; @@ -101,19 +129,39 @@ class BackupDialog extends React.Component< this.showMessage("Uploading"); this.props.handleLoadingDialog(true); - BackupUtil.backup( + let blob: any = await backup( this.props.books, this.props.notes, this.props.bookmarks, - this.handleFinish, - 3, - this.showMessage + false ); + if (!blob) { + this.showMessage("Backup Failed"); + this.props.handleLoadingDialog(false); + } + + let result = await WebdavUtil.UploadFile( + new File([blob], "data.zip", { + lastModified: new Date().getTime(), + type: blob.type, + }) + ); + if (result) { + this.handleFinish(); + } else { + this.showMessage("Upload failed, check your connection"); + this.props.handleLoadingDialog(false); + } } else { this.showMessage("Downloading"); this.props.handleLoadingDialog(true); - WebdavUtil.DownloadFile(this.handleFinish, this.showMessage); + let result = await WebdavUtil.DownloadFile(); + if (!result) { + this.showMessage("Download failed,network problem or no backup"); + } else { + this.handleFinish(); + } } break; default: diff --git a/src/components/dialogs/settingDialog/component.tsx b/src/components/dialogs/settingDialog/component.tsx index 6b7d0689..8f1534bc 100644 --- a/src/components/dialogs/settingDialog/component.tsx +++ b/src/components/dialogs/settingDialog/component.tsx @@ -6,12 +6,11 @@ import { Trans } from "react-i18next"; import i18n from "../../../i18n"; import { version } from "../../../../package.json"; import OtherUtil from "../../../utils/otherUtil"; -import SyncUtil from "../../../utils/syncUtils/common"; +import { changePath } from "../../../utils/syncUtils/common"; import { isElectron } from "react-device-detect"; import { dropdownList } from "../../../constants/dropdownList"; import { Tooltip } from "react-tippy"; -import RestoreUtil from "../../../utils/syncUtils/restoreUtil"; -import BackupUtil from "../../../utils/syncUtils/backupUtil"; +import { restore } from "../../../utils/syncUtils/restoreUtil"; import { settingList, langList, @@ -111,23 +110,14 @@ class SettingDialog extends React.Component< type: blobTemp.type, }); - RestoreUtil.restore( - fileTemp, - () => { - BackupUtil.backup( - this.props.books, - this.props.notes, - this.props.bookmarks, - () => { - this.props.handleMessage("Change Successfully"); - this.props.handleMessageBox(true); - }, - 5, - () => {} - ); - }, - true - ); + let result = await restore(fileTemp, true); + if (result) { + this.props.handleMessage("Change Successfully"); + this.props.handleMessageBox(true); + } else { + this.props.handleMessage("Change Failed"); + this.props.handleMessageBox(true); + } }; handleChangeLocation = async () => { const { dialog } = window.require("electron").remote; @@ -138,15 +128,21 @@ class SettingDialog extends React.Component< if (!path.filePaths[0]) { return; } - SyncUtil.changeLocation( + let result = await changePath( localStorage.getItem("storageLocation") ? localStorage.getItem("storageLocation") : ipcRenderer.sendSync("storage-location", "ping"), - path.filePaths[0], - this.props.handleMessage, - this.props.handleMessageBox, - this.syncFromLocation + path.filePaths[0] ); + if (result === 1) { + this.syncFromLocation(); + } else if (result === 2) { + this.props.handleMessage("Change Successfully"); + this.props.handleMessageBox(true); + } else { + this.props.handleMessage("Change Failed"); + this.props.handleMessageBox(true); + } localStorage.setItem("storageLocation", path.filePaths[0]); document.getElementsByClassName( "setting-dialog-location-title" diff --git a/src/components/emptyCover/emptyCover.css b/src/components/emptyCover/emptyCover.css index ef87a6ce..3eee2159 100644 --- a/src/components/emptyCover/emptyCover.css +++ b/src/components/emptyCover/emptyCover.css @@ -30,6 +30,7 @@ position: relative; top: 10px; margin-left: 5px; + word-wrap: break-word; } .cover-footer { width: 100%; diff --git a/src/components/importLocal/component.tsx b/src/components/importLocal/component.tsx index 84acde6e..1d37574a 100644 --- a/src/components/importLocal/component.tsx +++ b/src/components/importLocal/component.tsx @@ -174,7 +174,8 @@ class ImportLocal extends React.Component { const file_content = (event.target as any).result; let mobiFile = new MobiParser(file_content); let content: any = await mobiFile.render(isElectron); - if (typeof content === "object") { + //包含太多图片或者文件大于5m就不转换 + if (typeof content === "object" || file.size / 1024 / 1024 > 10) { result = BookUtil.generateBook( bookName, extension, diff --git a/src/components/textToSpeech/component.tsx b/src/components/textToSpeech/component.tsx index e1405528..0d7b8e03 100644 --- a/src/components/textToSpeech/component.tsx +++ b/src/components/textToSpeech/component.tsx @@ -110,11 +110,9 @@ class TextToSpeech extends React.Component< }; msg.onend = (event) => { - console.log(event, "event", new Date().getTime()); if (!(this.state.isAudioOn && this.props.isReading)) { return; } - console.log(event, "event1", new Date().getTime()); this.props.currentEpub.rendition.next().then(() => { this.handleAudio(); }); diff --git a/src/containers/header/component.tsx b/src/containers/header/component.tsx index e2018cac..47a23fbe 100644 --- a/src/containers/header/component.tsx +++ b/src/containers/header/component.tsx @@ -7,10 +7,12 @@ import { Trans } from "react-i18next"; import { HeaderProps, HeaderState } from "./interface"; import OtherUtil from "../../utils/otherUtil"; import UpdateInfo from "../../components/dialogs/updateInfo"; -import RestoreUtil from "../../utils/syncUtils/restoreUtil"; -import BackupUtil from "../../utils/syncUtils/backupUtil"; +import { restore } from "../../utils/syncUtils/restoreUtil"; +import { backup } from "../../utils/syncUtils/backupUtil"; import { Tooltip } from "react-tippy"; import { isElectron } from "react-device-detect"; +import { syncData } from "../../utils/syncUtils/common"; + class Header extends React.Component { constructor(props: HeaderProps) { super(props); @@ -114,40 +116,40 @@ class Header extends React.Component { lastModified: new Date().getTime(), type: blobTemp.type, }); - RestoreUtil.restore( - fileTemp, - () => { - this.setState({ isdataChange: false }); - //Check for data update - let storageLocation = localStorage.getItem("storageLocation") - ? localStorage.getItem("storageLocation") - : window - .require("electron") - .ipcRenderer.sendSync("storage-location", "ping"); - let sourcePath = path.join( - storageLocation, - "config", - "readerConfig.json" - ); + let result = await restore(fileTemp, true); + if (result) { + this.setState({ isdataChange: false }); + //Check for data update + let storageLocation = localStorage.getItem("storageLocation") + ? localStorage.getItem("storageLocation") + : window + .require("electron") + .ipcRenderer.sendSync("storage-location", "ping"); + let sourcePath = path.join( + storageLocation, + "config", + "readerConfig.json" + ); - fs.readFile(sourcePath, "utf8", (err, data) => { - if (err) { - console.error(err); - return; - } - const readerConfig = JSON.parse(data); - if ( - localStorage.getItem("lastSyncTime") && - readerConfig.lastSyncTime - ) { - localStorage.setItem("lastSyncTime", readerConfig.lastSyncTime); - } - }); - }, - true - ); + fs.readFile(sourcePath, "utf8", (err, data) => { + if (err) { + console.error(err); + return; + } + const readerConfig = JSON.parse(data); + if (localStorage.getItem("lastSyncTime") && readerConfig.lastSyncTime) { + localStorage.setItem("lastSyncTime", readerConfig.lastSyncTime); + } + }); + } + if (!result) { + this.props.handleMessage("Sync Failed"); + } else { + this.props.handleMessage("Sync Successfully"); + } + this.props.handleMessageBox(true); }; - syncToLocation = () => { + handleSync = () => { if (OtherUtil.getReaderConfig("isFirst") !== "no") { this.props.handleTipDialog(true); this.props.handleTip( @@ -164,21 +166,9 @@ class Header extends React.Component { .require("electron") .ipcRenderer.sendSync("storage-location", "ping"); let sourcePath = path.join(storageLocation, "config", "readerConfig.json"); - fs.readFile(sourcePath, "utf8", (err, data) => { - if (err) { - BackupUtil.backup( - this.props.books, - this.props.notes, - this.props.bookmarks, - () => { - this.props.handleMessage("Sync Successfully"); - this.props.handleMessageBox(true); - }, - 5, - () => {} - ); - console.log(err); - return; + fs.readFile(sourcePath, "utf8", async (err, data) => { + if (err || !data) { + this.syncToLocation(); } const readerConfig = JSON.parse(data); @@ -191,20 +181,28 @@ class Header extends React.Component { this.syncFromLocation(); } else { //否则就把Koodo中数据同步到同步文件夹 - BackupUtil.backup( - this.props.books, - this.props.notes, - this.props.bookmarks, - () => { - this.props.handleMessage("Sync Successfully"); - this.props.handleMessageBox(true); - }, - 5, - () => {} - ); + this.syncToLocation(); } }); }; + syncToLocation = async () => { + let timestamp = new Date().getTime().toString(); + OtherUtil.setReaderConfig("lastSyncTime", timestamp); + localStorage.setItem("lastSyncTime", timestamp); + let result = await backup( + this.props.books, + this.props.notes, + this.props.bookmarks, + true + ); + if (!result) { + this.props.handleMessage("Sync Failed"); + } else { + syncData(result as Blob, this.props.books, true); + this.props.handleMessage("Sync Successfully"); + } + this.props.handleMessageBox(true); + }; render() { return ( @@ -260,7 +258,7 @@ class Header extends React.Component { className="setting-icon-container" onClick={() => { // this.syncFromLocation(); - this.syncToLocation(); + this.handleSync(); }} style={{ left: "635px" }} > diff --git a/src/containers/lists/bookList/component.tsx b/src/containers/lists/bookList/component.tsx index e363608c..2bc11cc1 100644 --- a/src/containers/lists/bookList/component.tsx +++ b/src/containers/lists/bookList/component.tsx @@ -17,7 +17,7 @@ import DeletePopup from "../../../components/dialogs/deletePopup"; import Empty from "../../emptyPage"; import { Redirect, withRouter } from "react-router-dom"; import ViewMode from "../../../components/viewMode"; -import BackUtil from "../../../utils/syncUtils/backupUtil"; +import { backup } from "../../../utils/syncUtils/backupUtil"; import { isElectron } from "react-device-detect"; class BookList extends React.Component { @@ -126,7 +126,6 @@ class BookList extends React.Component { ); } - return books.map((item: BookModel, index: number) => { return this.props.viewMode === "list" ? ( { //兼容之前的版本 localforage.getItem(this.props.books[0].key).then((result) => { if (result) { - BackUtil.backup( + backup( this.props.books, this.props.notes, this.props.bookmarks, - () => {}, - 4, - () => {} + false ); } }); diff --git a/src/containers/sidebar/component.tsx b/src/containers/sidebar/component.tsx index c3c3feaa..999c824b 100644 --- a/src/containers/sidebar/component.tsx +++ b/src/containers/sidebar/component.tsx @@ -126,6 +126,7 @@ class Sidebar extends React.Component { )} position="top" trigger="mouseenter" + distance={25} > diff --git a/src/utils/syncUtils/backupUtil.tsx b/src/utils/syncUtils/backupUtil.tsx index 2d987a0d..ea628887 100644 --- a/src/utils/syncUtils/backupUtil.tsx +++ b/src/utils/syncUtils/backupUtil.tsx @@ -1,125 +1,33 @@ -import FileSaver from "file-saver"; import BookModel from "../../model/Book"; import NoteModel from "../../model/Note"; import BookmarkModel from "../../model/Bookmark"; -import DropboxUtil from "./dropbox"; -import WebdavUtil from "./webdav"; -import localforage from "localforage"; -import SyncUtil, { moveData } from "./common"; -import BookUtil from "../fileUtils/bookUtil"; -import OtherUtil from "../otherUtil"; +import { zipBook, zipConfig } from "./common"; let JSZip = (window as any).JSZip; -class BackupUtil { - static backup = async ( - bookArr: BookModel[], - notes: NoteModel[], - bookmarks: BookmarkModel[], - handleFinish: () => void, - driveIndex: number, - showMessage: (message: string) => void - ) => { +export const backup = ( + bookArr: BookModel[], + notes: NoteModel[], + bookmarks: BookmarkModel[], + isSync: boolean +) => { + return new Promise(async (resolve, reject) => { let zip = new JSZip(); let books = bookArr; //0表示备份到本地,1表示备份到dropbox,2表示备份到onedrive,3表示备份到webdav,4表示把indexeddb中的数据转移到uploads文件夹中,5表示同步数据到本地 - if (driveIndex !== 5) { - let bookZip = zip.folder("book"); - let data: any = []; - books && - books.forEach((item) => { - data.push( - driveIndex === 4 - ? localforage.getItem(item.key) - : BookUtil.fetchBook(item.key) - ); - }); - let results = await Promise.all(data); - for (let i = 0; i < books.length; i++) { - bookZip.file(`${books[i].key}`, results[i]); - } - } else { - let timestamp = new Date().getTime().toString(); - OtherUtil.setReaderConfig("lastSyncTime", timestamp); - - localStorage.setItem("lastSyncTime", timestamp); + let result = await zipConfig(zip, books, notes, bookmarks); + if (!result) resolve(false); + if (!isSync) { + await zipBook(zip, books); } - let configZip = zip.folder("config"); - configZip - .file("notes.json", JSON.stringify(notes)) - .file("books.json", JSON.stringify(books)) - .file("bookmarks.json", JSON.stringify(bookmarks)) - .file("readerConfig.json", localStorage.getItem("readerConfig") || "") - .file("themeColors.json", localStorage.getItem("themeColors") || "") - .file( - "bookSortCode.json", - localStorage.getItem("bookSortCode") || - JSON.stringify({ sort: 0, order: 2 }) - ) - .file( - "noteSortCode.json", - localStorage.getItem("noteSortCode") || - JSON.stringify({ sort: 2, order: 2 }) - ) - .file("readingTime.json", localStorage.getItem("readingTime") || "") - .file("recentBooks.json", localStorage.getItem("recentBooks") || []) - .file("favoriteBooks.json", localStorage.getItem("favoriteBooks") || []) - .file("shelfList.json", localStorage.getItem("shelfList") || []) - .file("noteTags.json", localStorage.getItem("noteTags") || []) - .file("pdfjs.history.json", localStorage.getItem("pdfjs.history") || []) - .file( - "recordLocation.json", - localStorage.getItem("recordLocation") || "" - ); - let year = new Date().getFullYear(), - month = new Date().getMonth() + 1, - day = new Date().getDate(); - zip .generateAsync({ type: "blob" }) .then((blob: any) => { - switch (driveIndex) { - case 0: - handleFinish(); - FileSaver.saveAs( - blob, - `${year}-${month <= 9 ? "0" + month : month}-${ - day <= 9 ? "0" + day : day - }.zip` - ); - break; - case 1: - DropboxUtil.UploadFile(blob, handleFinish, showMessage); - break; - case 2: - break; - case 3: - WebdavUtil.UploadFile( - new File([blob], "data.zip", { - lastModified: new Date().getTime(), - type: blob.type, - }), - handleFinish, - showMessage - ); - break; - case 4: - moveData(blob, 4, books); - - break; - case 5: - handleFinish(); - SyncUtil.syncData(blob, 5, [], handleFinish); - - break; - default: - break; - } + resolve(blob); }) .catch((err: any) => { console.log(err); + resolve(false); }); - }; -} - -export default BackupUtil; + }); +}; diff --git a/src/utils/syncUtils/common.tsx b/src/utils/syncUtils/common.tsx index 8191c2b2..a0415bf0 100644 --- a/src/utils/syncUtils/common.tsx +++ b/src/utils/syncUtils/common.tsx @@ -1,6 +1,28 @@ import BookModel from "../../model/Book"; import localforage from "localforage"; +import BookUtil from "../fileUtils/bookUtil"; +import NoteModel from "../../model/Note"; +import BookmarkModel from "../../model/Bookmark"; +import { isElectron } from "react-device-detect"; +let JSZip = (window as any).JSZip; +let configArr = [ + "notes", + "books", + "bookmarks", + "readerConfig", + "noteTags", + "themeColors", + "bookSortCode", + "noteSortCode", + "readingTime", + "recentBooks", + "favoriteBooks", + "favoriteBooks", + "shelfList", + "pdfjs.history", + "recordLocation", +]; export function getParamsFromUrl() { var hashParams: any = {}; var e, @@ -14,6 +36,7 @@ export function getParamsFromUrl() { } return hashParams; } +//移动文件到指定路径 export const moveData = ( blob, driveIndex, @@ -64,15 +87,9 @@ export const moveData = ( } }; }; - -class SyncUtil { - static changeLocation( - oldPath: string, - newPath: string, - handleMessage: (message: string) => void, - handleMessageBox: (isShow: boolean) => void, - syncFromLocation: () => void = () => {} - ) { +//改变数据存储路径 +export const changePath = (oldPath: string, newPath: string) => { + return new Promise((resolve, reject) => { const fs = window.require("fs-extra"); try { fs.readdir(newPath, (err, files: string[]) => { @@ -84,27 +101,23 @@ class SyncUtil { }); if (isConfiged) { localStorage.setItem("storageLocation", newPath); - syncFromLocation(); + resolve(1); } else { fs.copy(oldPath, newPath, function (err) { if (err) return; fs.emptyDirSync(oldPath); - handleMessage("Change Successfully"); - handleMessageBox(true); + resolve(2); }); } }); } catch (error) { - handleMessage("Change Failed"); - handleMessageBox(true); + console.log(error); + resolve(0); } - } - static syncData( - blob, - driveIndex, - books: BookModel[] = [], - handleFinish: () => void = () => {} - ) { + }); +}; +export const syncData = (blob: Blob, books: BookModel[] = [], isSync: true) => { + return new Promise((resolve, reject) => { let file = new File([blob], "config.zip", { lastModified: new Date().getTime(), type: blob.type, @@ -127,17 +140,161 @@ class SyncUtil { var zip = new AdmZip(path.join(dataPath, file.name)); zip.extractAllTo(/*target path*/ dataPath, /*overwrite*/ true); - if (driveIndex === 4) { + if (!isSync) { let deleteBooks = books.map((item) => { return localforage.removeItem(item.key); }); await Promise.all(deleteBooks); - } - if (driveIndex === 5) { - handleFinish(); + resolve(true); + } else { + resolve(true); } }; - } -} + }); +}; -export default SyncUtil; +export const zipBook = (zip: any, books: BookModel[]) => { + return new Promise(async (resolve, reject) => { + let bookZip = zip.folder("book"); + let data: any = []; + books && + books.forEach((item) => { + data.push( + !isElectron + ? localforage.getItem(item.key) + : BookUtil.fetchBook(item.key) + ); + }); + try { + let results = await Promise.all(data); + for (let i = 0; i < books.length; i++) { + bookZip.file(`${books[i].key}`, results[i]); + } + resolve(true); + } catch (error) { + resolve(false); + } + }); +}; + +export const unzipConfig = (file: File) => { + return new Promise((resolve, reject) => { + let zip = new JSZip(); + let count = 0; + configArr.forEach((item) => { + zip + .loadAsync(file) + .then((content: any) => { + return content.files[ + content.files[`${item}.json`] + ? `${item}.json` + : `config/${item}.json` + ].async("text"); + }) + .then(async (text: any) => { + if (text) { + if (item === "notes" || item === "books" || item === "bookmarks") { + localforage.setItem(item, JSON.parse(text)); + } else { + localStorage.setItem(item, text); + } + } + count++; + if (count === configArr.length) { + resolve(true); + } + }) + .catch((err: any) => { + reject(false); + console.log(err, "Error happen"); + }); + }); + }); +}; + +export const unzipBook = (file: File) => { + return new Promise((resolve, reject) => { + localforage.getItem("books").then((value: any) => { + let zip = new JSZip(); + let count = 0; + value && + value.length > 0 && + value.forEach((item: any) => { + zip + .loadAsync(file) + .then((content: any) => { + if (content.files[`book/${item.key}`]) { + return content.files[`book/${item.key}`].async("arraybuffer"); + } else if (content.files[`${item.key}`]) { + return content.files[`${item.key}`].async("arraybuffer"); + } + if ( + content.files[`book/${item.name}.pdf`] && + item.description === "pdf" + ) { + //兼容之前的版本 + return content.files[`book/${item.name}.pdf`].async( + "arraybuffer" + ); // a promise of "Hello World\n" + } else if (content.files[`book/${item.name}.epub`]) { + return content.files[`book/${item.name}.epub`].async( + "arraybuffer" + ); // a promise of "Hello World\n" + } + }) + .then(async (book: any) => { + await BookUtil.addBook(item.key, book); + count++; + if (count === value.length) { + resolve(true); + } + }) + .catch((err: any) => { + resolve(false); + }); + }); + }); + }); +}; + +export const zipConfig = ( + zip: any, + books: BookModel[], + notes: NoteModel[], + bookmarks: BookmarkModel[] +) => { + return new Promise((resolve, reject) => { + try { + let configZip = zip.folder("config"); + configZip + .file("notes.json", JSON.stringify(notes)) + .file("books.json", JSON.stringify(books)) + .file("bookmarks.json", JSON.stringify(bookmarks)) + .file("readerConfig.json", localStorage.getItem("readerConfig") || "") + .file("themeColors.json", localStorage.getItem("themeColors") || "") + .file( + "bookSortCode.json", + localStorage.getItem("bookSortCode") || + JSON.stringify({ sort: 0, order: 2 }) + ) + .file( + "noteSortCode.json", + localStorage.getItem("noteSortCode") || + JSON.stringify({ sort: 2, order: 2 }) + ) + .file("readingTime.json", localStorage.getItem("readingTime") || "") + .file("recentBooks.json", localStorage.getItem("recentBooks") || []) + .file("favoriteBooks.json", localStorage.getItem("favoriteBooks") || []) + .file("shelfList.json", localStorage.getItem("shelfList") || []) + .file("noteTags.json", localStorage.getItem("noteTags") || []) + .file("pdfjs.history.json", localStorage.getItem("pdfjs.history") || []) + .file( + "recordLocation.json", + localStorage.getItem("recordLocation") || "" + ); + resolve(true); + } catch (error) { + resolve(false); + } + }); +}; diff --git a/src/utils/syncUtils/dropbox.tsx b/src/utils/syncUtils/dropbox.tsx index 02e5b42c..aa4bf4ea 100644 --- a/src/utils/syncUtils/dropbox.tsx +++ b/src/utils/syncUtils/dropbox.tsx @@ -1,76 +1,66 @@ -import RestoreUtil from "./restoreUtil"; +import { restore } from "./restoreUtil"; import OtherUtil from "../otherUtil"; var Dropbox = (window as any).Dropbox; class DropboxUtil { - static UploadFile( - blob: any, - handleFinish: () => void, - showMessage: (message: string) => void - ) { - var ACCESS_TOKEN = OtherUtil.getReaderConfig("dropbox_token") || ""; - let year = new Date().getFullYear(), - month = new Date().getMonth() + 1, - day = new Date().getDate(); - var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN }); - const file = new File([blob], "data.zip"); - - dbx - .filesUpload({ - path: "/data.zip", - contents: file, - }) - .then(function (response: any) { - console.log(response, "上传成功"); - dbx - .filesCopyV2({ - from_path: "/data.zip", - to_path: - "/" + - `${year}-${month <= 9 ? "0" + month : month}-${ - day <= 9 ? "0" + day : day - }.zip`, - }) - .then(function (response: any) { - console.log(response, "上传成功"); - handleFinish(); - }) - .catch(function (error: any) { - console.error(error, "上传失败"); - showMessage("Upload failed, check your connection"); - }); - }) - .catch(function (error: any) { - console.error(error, "上传失败"); - showMessage("Upload failed, check your connection"); - }); - return false; + static UploadFile(blob: any) { + return new Promise((resolve, reject) => { + var ACCESS_TOKEN = OtherUtil.getReaderConfig("dropbox_token") || ""; + var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN }); + const file = new File([blob], "data.zip"); + let date = new Date().getTime(); + dbx + .filesUpload({ + path: `/${date}/data.zip`, + contents: file, + }) + .then(function (response: any) { + console.log(response, "上传成功"); + resolve(true); + }) + .catch(function (error: any) { + console.error(error, "上传失败"); + resolve(false); + }); + }); } - static DownloadFile( - handleFinish: () => void, - showMessage: (message: string) => void, - isSync: boolean = false - ) { - var ACCESS_TOKEN = OtherUtil.getReaderConfig("dropbox_token") || ""; - var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN }); - dbx - .filesDownload({ - path: isSync ? "/config.zip" : "/data.zip", - }) - .then(function (data: any) { - let file = data.result.fileBlob; - file.lastModifiedDate = new Date(); - file.name = "data.zip"; - RestoreUtil.restore(file, handleFinish, isSync); - }) - .catch(function (error: any) { - showMessage("Download failed,network problem or no backup"); - - console.error(error); - }); - - return false; + static DownloadFile() { + return new Promise((resolve, reject) => { + var ACCESS_TOKEN = OtherUtil.getReaderConfig("dropbox_token") || ""; + var dbx = new Dropbox.Dropbox({ accessToken: ACCESS_TOKEN }); + dbx + .filesListFolder({ path: "" }) + .then(function (response) { + let folderArr: string[] = []; + response.result.entries.forEach((item) => { + if (!isNaN(parseInt(item.name))) folderArr.push(item.name); + }); + let folder = folderArr.sort().reverse()[0]; + dbx + .filesDownload({ + path: `/${folder}/data.zip`, + }) + .then(async (data: any) => { + let file = data.result.fileBlob; + file.lastModifiedDate = new Date(); + file.name = "data.zip"; + let result = await restore(file); + if (result) { + resolve(true); + } else { + resolve(false); + } + }) + .catch(function (error: any) { + console.error(error); + resolve(false); + }); + }) + .catch(function (error) { + console.error(error); + }); + }); } } diff --git a/src/utils/syncUtils/restoreUtil.tsx b/src/utils/syncUtils/restoreUtil.tsx index 5de92aa2..6df18401 100644 --- a/src/utils/syncUtils/restoreUtil.tsx +++ b/src/utils/syncUtils/restoreUtil.tsx @@ -1,97 +1,21 @@ -import localforage from "localforage"; -import BookUtil from "../fileUtils/bookUtil"; +import { unzipBook, unzipConfig } from "./common"; -let JSZip = (window as any).JSZip; - -class RestoreUtil { - static restore = (file: any, handleFinish: () => void, isSync = false) => { - let configArr = [ - "notes", - "books", - "bookmarks", - "readerConfig", - "noteTags", - "themeColors", - "bookSortCode", - "noteSortCode", - "readingTime", - "recentBooks", - "favoriteBooks", - "favoriteBooks", - "shelfList", - "pdfjs.history", - "recordLocation", - ]; - let zip = new JSZip(); - - // more files ! - configArr.forEach((item) => { - zip - .loadAsync(file) - .then((content: any) => { - // you now have every files contained in the loaded zip - return content.files[ - content.files[`${item}.json`] - ? `${item}.json` - : `config/${item}.json` - ].async("text"); // a promise of "Hello World\n" - }) - .then((text: any) => { - if (text) { - if (item === "notes" || item === "books" || item === "bookmarks") { - localforage.setItem(item, JSON.parse(text)); - } else { - localStorage.setItem(item, text); - } - } - }) - .then(() => { - if (item === "books" && !isSync) { - localforage.getItem("books").then((value: any) => { - let zip = new JSZip(); - value && - value.length > 0 && - value.forEach((item: any) => { - zip - .loadAsync(file) - .then((content: any) => { - if (content.files[`book/${item.key}`]) { - return content.files[`book/${item.key}`].async( - "arraybuffer" - ); - } - //兼容之前的版本 - if ( - content.files[`book/${item.name}.pdf`] && - item.description === "pdf" - ) { - return content.files[`book/${item.name}.pdf`].async( - "arraybuffer" - ); // a promise of "Hello World\n" - } else if (content.files[`book/${item.name}.epub`]) { - return content.files[`book/${item.name}.epub`].async( - "arraybuffer" - ); // a promise of "Hello World\n" - } - }) - .then(async (book: any) => { - await BookUtil.addBook(item.key, book); - }) - .catch((err: any) => { - console.log(err, "Error occurs"); - }); - }); - }); - } - }) - .catch((err: any) => { - console.log(err, "Error happen"); - }); - }); - setTimeout(() => { - handleFinish(); - }, 1000); - }; -} - -export default RestoreUtil; +export const restore = (file: any, isSync = false) => { + return new Promise(async (resolve, reject) => { + let result = await unzipConfig(file); + if (result) { + if (isSync) { + resolve(true); + } else { + let res = await unzipBook(file); + if (res) { + resolve(true); + } else { + resolve(false); + } + } + } else { + resolve(false); + } + }); +}; diff --git a/src/utils/syncUtils/webdav.tsx b/src/utils/syncUtils/webdav.tsx index 45db8b1c..8a7da2a0 100644 --- a/src/utils/syncUtils/webdav.tsx +++ b/src/utils/syncUtils/webdav.tsx @@ -1,101 +1,98 @@ -import RestoreUtil from "./restoreUtil"; +import { restore } from "./restoreUtil"; import OtherUtil from "../otherUtil"; class WebdavUtil { - static UploadFile = async ( - file: any, - handleFinish: () => void, - showMessage: (message: string) => void - ) => { - const { createClient } = window.require("webdav"); - let { url, username, password } = JSON.parse( - OtherUtil.getReaderConfig("webdav_token") || "" - ); - const client = createClient(url, { - username, - password, + static UploadFile = async (file: any) => { + return new Promise(async (resolve, reject) => { + const { createClient } = window.require("webdav"); + let { url, username, password } = JSON.parse( + OtherUtil.getReaderConfig("webdav_token") || "" + ); + const client = createClient(url, { + username, + password, + }); + var wfs = window.require("webdav-fs")(url, { + username: username, + password: password, + }); + if ((await client.exists("/KoodoReader")) === false) { + await client.createDirectory("/KoodoReader"); + } + wfs.writeFile("/KoodoReader/data.zip", file, "binary", function (err) { + console.log(err); + if (err) resolve(false); + let year = new Date().getFullYear(), + month = new Date().getMonth() + 1, + day = new Date().getDate(); + client + .copyFile( + "/KoodoReader/data.zip", + "/KoodoReader/" + + `${year}-${month <= 9 ? "0" + month : month}-${ + day <= 9 ? "0" + day : day + }.zip` + ) + .then(() => { + resolve(true); + }) + .catch(() => { + resolve(false); + }); + }); }); - var wfs = window.require("webdav-fs")(url, { - username: username, - password: password, - }); - if ((await client.exists("/KoodoReader")) === false) { - await client.createDirectory("/KoodoReader"); - } - wfs.writeFile("/KoodoReader/data.zip", file, "binary", function (err) { - console.log(err); - if (err) showMessage("Upload failed, check your connection"); - let year = new Date().getFullYear(), - month = new Date().getMonth() + 1, - day = new Date().getDate(); - client - .copyFile( - "/KoodoReader/data.zip", - "/KoodoReader/" + - `${year}-${month <= 9 ? "0" + month : month}-${ - day <= 9 ? "0" + day : day - }.zip` - ) - .then(() => { - handleFinish(); - }) - .catch(() => { - showMessage("Upload failed, check your connection"); + }; + static DownloadFile = async () => { + return new Promise(async (resolve, reject) => { + const fs = window.require("fs"); + const path = window.require("path"); + const { createClient } = window.require("webdav"); + const { remote, app } = window.require("electron"); + const configDir = (app || remote.app).getPath("userData"); + const dirPath = path.join(configDir, "uploads"); + const request = window.require("request"); + let { url, username, password } = JSON.parse( + OtherUtil.getReaderConfig("webdav_token") || "" + ); + const client = createClient(url, { + username, + password, + }); + if ((await client.exists("/KoodoReader/data.zip")) === false) { + resolve(false); + } + const downloadLink: string = client.getFileDownloadLink( + "/KoodoReader/data.zip" + ); + let stream = fs.createWriteStream(path.join(dirPath, `data.zip`)); + request(downloadLink) + .pipe(stream) + .on("close", async (err) => { + if (err) { + console.log(err); + resolve(false); + } else { + var data = fs.readFileSync(path.join(dirPath, `data.zip`)); + let blobTemp: any = new Blob([data], { type: "application/zip" }); + let fileTemp = new File([blobTemp], "data.zip", { + lastModified: new Date().getTime(), + type: blobTemp.type, + }); + let result = await restore(fileTemp); + if (!result) resolve(false); + try { + const fs_extra = window.require("fs-extra"); + fs_extra.remove(path.join(dirPath, `data.zip`), (error: any) => { + if (error) resolve(false); + resolve(true); + }); + } catch (e) { + console.log("error removing ", path.join(dirPath, `data.zip`)); + resolve(false); + } + } }); }); - - return false; - }; - static DownloadFile = async ( - handleFinish: () => void, - showMessage: (message: string) => void - ) => { - const fs = window.require("fs"); - const path = window.require("path"); - const { createClient } = window.require("webdav"); - const { remote, app } = window.require("electron"); - const configDir = (app || remote.app).getPath("userData"); - const dirPath = path.join(configDir, "uploads"); - const request = window.require("request"); - let { url, username, password } = JSON.parse( - OtherUtil.getReaderConfig("webdav_token") || "" - ); - const client = createClient(url, { - username, - password, - }); - if ((await client.exists("/KoodoReader/data.zip")) === false) { - showMessage("Download failed,network problem or no backup"); - } - const downloadLink: string = client.getFileDownloadLink( - "/KoodoReader/data.zip" - ); - let stream = fs.createWriteStream(path.join(dirPath, `data.zip`)); - request(downloadLink) - .pipe(stream) - .on("close", function (err) { - if (err) { - console.log(err); - } else { - var data = fs.readFileSync(path.join(dirPath, `data.zip`)); - let blobTemp: any = new Blob([data], { type: "application/zip" }); - let fileTemp = new File([blobTemp], "data.zip", { - lastModified: new Date().getTime(), - type: blobTemp.type, - }); - RestoreUtil.restore(fileTemp, handleFinish); - try { - const fs_extra = window.require("fs-extra"); - fs_extra.remove(path.join(dirPath, `data.zip`), (error: any) => { - if (error) throw error; - }); - } catch (e) { - console.log("error removing ", path.join(dirPath, `data.zip`)); - } - } - }); - - return false; }; }