diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index c7580deac..4721efc41 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -774,16 +774,16 @@ "18": "Get community user account failed", "19": "User information has expired, please log in again", "20": "Cannot be converted to heading when including sub-documents", - "21": "Backup completed", - "22": "Backuping, please wait...", - "23": "Backup failed: %s", + "21": "TODO", + "22": "TODO", + "23": "TODO", "24": "TODO", "25": "The attribute name only supports English letters and digits", "26": "Please initialize the data repo key first in [Settings - About - Data repo key]", "27": "Data integrity check failed", "28": "TODO", "29": "This feature requires paid subscription (If you have subscribed, please refresh or log in again in settings - account)", - "30": "Failed to obtain cloud backup info", + "30": "Failed to obtain cloud info", "31": "Account authentication failed, please login again", "32": "Failed to remove cloud notebook", "33": "Insufficient permissions to read and write files or access to the network, please check the permissions of the workspace folder and the settings of the anti-virus software/firewall. If you have run SiYuan as an administrator before, please consider switching to a new workspace directory, and do not run it as an administrator in the future (the current workspace directory may no longer be accessible by ordinary users)", @@ -814,14 +814,14 @@ "58": "After the index is rebuilt, the interface will be automatically refreshed later...", "59": "Failed to set sync ignore list", "60": "Failed to get the update package: %s", - "61": "Uploading, please wait...", - "62": "The recovery is complete, and the index will be rebuilt...", + "61": "TODO", + "62": "TODO", "63": "Recovering, please wait...", "64": "There are [%d] files in total, it will take some time to index, please wait...", "65": "Exporting data...", "66": "Data file [%s] created", "67": "Uploaded at %s, downloaded at %s", - "68": "Downloading, please wait...", + "68": "TODO", "69": "Download completed", "70": "Copy notebook [%s] file [%s] failed: %s", "71": "Failed to insert asset file, please reopen the document", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 5934de329..b47f1d367 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -774,16 +774,16 @@ "18": "Falló la obtención de la cuenta de usuario de la comunidad", "19": "La información del usuario ha caducado, por favor, inicie sesión de nuevo", "20": "No se puede convertir en título al incluir subdocumentos", - "21": "Copia de seguridad completada", - "22": "Haciendo copia de seguridad, por favor espere...", - "23": "Copia de seguridad fallida: %s", + "21": "TODO", + "22": "TODO", + "23": "TODO", "24": "TODO", "25": "El nombre del atributo sólo admite letras y dígitos en inglés", "26": "Por favor, inicialice primero la clave de repositorio de datos en [Configuración - Acerca de - Clave de repositorio de datos]", "27": "Falló la comprobación de la integridad de los datos", "28": "TODO", "29": "Esta función requiere una suscripción de pago (Si se ha suscrito, actualice o vuelva a conectarse en configuración - cuenta)", - "30": "Fallo en la obtención de la información de la copia de seguridad en la nube", + "30": "No se pudo obtener la información de la nube", "31": "Falló la autentificación de la cuenta, por favor, inicie sesión de nuevo", "32": "Fallo en la eliminación de la libreta en la nube", "33": "Permisos insuficientes para leer y escribir archivos o acceso a la red, por favor comprueba los permisos de la carpeta del espacio de trabajo y la configuración del software antivirus/firewall. Si has ejecutado SiYuan como administrador antes, por favor considera cambiar a un nuevo directorio de espacio de trabajo, y no lo ejecutes como administrador en el futuro (el directorio de espacio de trabajo actual puede que ya no sea accesible por los usuarios ordinarios)", @@ -814,14 +814,14 @@ "58": "Después de reconstruir el índice, la interfaz se actualizará automáticamente más tarde...", "59": " Falló la configuración de sincronización de la lista de ignorados", "60": "Fallo al obtener el paquete de actualización: %s", - "61": "Cargando, por favor espere...", - "62": "La recuperación se ha completado y el índice se reconstruirá...", + "61": "TODO", + "62": "TODO", "63": "Recuperando, por favor espere...", "64": "Hay [%d] archivos en total, tardará un tiempo en indexarse, por favor espere...", "65": "Exportando datos...", "66": "Archivo de datos [%s] creado", "67": "Cargado en %s, descargado en %s", - "68": "Descargando, por favor espere...", + "68": "TODO", "69": "Descarga completada", "70": "Error en la copia del cuaderno [%s] del archivo [%s]: %s", "71": "Fallo en la inserción del archivo de activos, por favor reabra el documento", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index d6ed6803e..ff6da4a64 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -774,16 +774,16 @@ "18": "Échec de la récupération du compte utilisateur communautaire", "19": "Les informations de l'utilisateur ont expiré, veuillez vous connecter à nouveau.", "20": "Ne peut pas être converti en titre lorsque des sous-documents sont inclus.", - "21": "Sauvegarde terminée", - "22": "En cours de sauvegarde, veuillez patienter....", - "23": "La sauvegarde a échoué : %s", + "21": "TODO", + "22": "TODO", + "23": "TODO", "24": "TODO", "25": "Le nom de l'attribut ne supporte que les lettres et les chiffres anglais.", "26": "Veuillez d'abord initialiser la clé du référentiel de données dans [Paramètres - À propos - Clé du référentiel de données]", "27": "La vérification de l'intégrité des données a échoué", "28": "TODO", "29": "Cette fonctionnalité nécessite un abonnement payant (Si vous êtes déjà abonné, Rafraîchissez ou connectez - vous à nouveau dans Paramètres - compte)", - "30": "Impossible d'obtenir des informations sur la sauvegarde dans le Cloud.", + "30": "Échec de l'obtention des informations sur le cloud", "31": "L'authentification du compte a échoué, veuillez vous reconnecter", "32": "Échec de la suppression de carnet de notes du Cloud", "33": "Autorisations insuffisantes pour lire et écrire des fichiers ou accéder au réseau, veuillez vérifier les autorisations du dossier de l'espace de travail et les paramètres du logiciel anti-virus/pare-feu. Si vous avez déjà exécuté SiYuan en tant qu'administrateur, envisagez de passer à un nouveau répertoire d'espace de travail et ne l'exécutez plus en tant qu'administrateur à l'avenir (le répertoire d'espace de travail actuel peut ne plus être accessible aux utilisateurs ordinaires) ", @@ -814,14 +814,14 @@ "58": "Une fois l'index reconstruit, l'interface sera automatiquement rafraîchie ultérieurement...", "59": "Échec de la définition de la liste des ignores de synchronisation", "60": "Échec de la récupération du paquet de mise à jour : %s", - "61": "En cours de transfert, veuillez patienter...", - "62": "La récupération est terminée, et l'index sera reconstruit...", + "61": "TODO", + "62": "TODO", "63": "Récupération, veuillez patienter...", "64": "Il y a [%d] fichiers au total, l'indexation prendra un certain temps, veuillez patienter...", "65": "Exportation des données...", "66": "Fichier de données [%s] créé", "67": "Transféré à %s, téléchargé à %s", - "68": "En cours de téléchargement, veuillez patienter...", + "68": "TODO", "69": "Téléchargement terminé", "70": "La copie du carnet de notes [%s] du fichier [%s] a échoué : %s", "71": "L'insertion du fichier asset a échoué, veuillez rouvrir le document.", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 434a278ed..c6e2d8188 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -774,16 +774,16 @@ "18": "獲取社區用戶帳號失敗", "19": "使用者資訊已過期,請重新登入帳號", "20": "包含子文檔時無法轉換為標題", - "21": "備份完畢", - "22": "正在備份,請稍等...", - "23": "備份失敗:%s", + "21": "TODO", + "22": "TODO", + "23": "TODO", "24": "TODO", "25": "屬性名僅支援英文字母和阿拉伯數字", "26": "請先在 [設置 - 關於 - 數據倉庫密鑰] 中初始化數據倉庫密鑰", "27": "數據完整性校驗失敗", "28": "TODO", "29": "該功能需要付費訂閱(如果你已經訂閱,請在設定-帳號中重繪或者重新登入)", - "30": "獲取雲端備份資訊失敗", + "30": "獲取雲端資訊失敗", "31": "帳號鑒權失敗,請重新登入帳號", "32": "刪除雲端筆記本失敗", "33": "讀寫檔或存取網路權限不足,請檢查工作空間資料夾權限和防毒軟體/防火牆的設置。如果你曾經使用管理員身份運行過思源,請考慮切換到新的工作空間目錄,後續請勿使用管理員身份運行(當前的工作空間目錄可能已經無法使用普通用戶存取)", @@ -814,14 +814,14 @@ "58": "重建索引完畢,稍後將自動重新整理介面...", "59": "設置同步忽略列表失敗", "60": "獲取更新包失敗:%s", - "61": "上傳中,請稍等...", - "62": "恢復完畢,即將重建索引...", + "61": "TODO", + "62": "TODO", "63": "正在恢復,請稍等...", "64": "共有檔 [%d] 個,需要一些時間進行索引,請稍等...", "65": "導出數據中...", "66": "已創建資料檔案 [%s]", "67": "上傳於 %s,下載於 %s", - "68": "下載中,請稍等...", + "68": "TODO", "69": "下載完畢", "70": "複製筆記本 [%s] 下的檔 [%s] 失敗:%s", "71": "插入資料檔失敗,請重新打開文檔", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 633784d05..f3cf1eecd 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -775,16 +775,16 @@ "18": "获取社区用户账号失败", "19": "用户信息已过期,请重新登录账号", "20": "包含子文档时无法转换为标题", - "21": "备份完毕", - "22": "正在备份,请稍等...", - "23": "备份失败:%s", + "21": "TODO", + "22": "TODO", + "23": "TODO", "24": "TODO", "25": "属性名仅支持英文字母和阿拉伯数字", "26": "请先在 [设置 - 关于 - 数据仓库密钥] 中初始化数据仓库密钥", "27": "数据完整性校验失败", "28": "TODO", "29": "该功能需要付费订阅(如果你已经订阅,请在 设置 - 账号中刷新或者重新登录)", - "30": "获取云端备份信息失败", + "30": "获取云端信息失败", "31": "账号鉴权失败,请重新登录账号", "32": "删除云端笔记本失败", "33": "读写文件或访问网络权限不足,请检查工作空间文件夹权限和杀毒软件/防火墙的设置。如果你曾经使用管理员身份运行过思源,请考虑切换到新的工作空间目录,后续请勿使用管理员身份运行(当前的工作空间目录可能已经无法使用普通用户访问)", @@ -815,14 +815,14 @@ "58": "重建索引完毕,稍后将自动刷新界面...", "59": "设置同步忽略列表失败", "60": "获取更新包失败:%s", - "61": "上传中,请稍等...", - "62": "恢复完毕,即将重建索引...", + "61": "TODO", + "62": "TODO", "63": "正在恢复,请稍等...", "64": "共有文件 [%d] 个,需要一些时间进行索引,请稍等...", "65": "导出数据中...", "66": "已创建数据文件 [%s]", "67": "上传于 %s,下载于 %s", - "68": "下载中,请稍等...", + "68": "TODO", "69": "下载完毕", "70": "复制笔记本 [%s] 下的文件 [%s] 失败:%s", "71": "插入资源文件失败,请重新打开文档", diff --git a/kernel/api/backup.go b/kernel/api/backup.go deleted file mode 100644 index 0ea8dc613..000000000 --- a/kernel/api/backup.go +++ /dev/null @@ -1,129 +0,0 @@ -// SiYuan - Build Your Eternal Digital Garden -// Copyright (c) 2020-present, b3log.org -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package api - -import ( - "net/http" - - "github.com/88250/gulu" - "github.com/dustin/go-humanize" - "github.com/gin-gonic/gin" - "github.com/siyuan-note/siyuan/kernel/model" - "github.com/siyuan-note/siyuan/kernel/util" -) - -func removeCloudBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - err := model.RemoveCloudBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } -} - -func downloadCloudBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - err := model.DownloadBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } -} - -func uploadLocalBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - err := model.UploadBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } -} - -func recoverLocalBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - err := model.RecoverLocalBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } -} - -func createLocalBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - err := model.CreateLocalBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } -} - -func getLocalBackup(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - backup, err := model.GetLocalBackup() - if nil != err { - ret.Code = -1 - ret.Msg = err.Error() - return - } - - ret.Data = map[string]interface{}{ - "backup": backup, - } -} - -func getCloudSpace(c *gin.Context) { - ret := gulu.Ret.NewResult() - defer c.JSON(http.StatusOK, ret) - - sync, backup, size, assetSize, totalSize, err := model.GetCloudSpace() - if nil != err { - ret.Code = 1 - ret.Msg = err.Error() - util.PushErrMsg(err.Error(), 3000) - return - } - - hTrafficUploadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficUpload)) - hTrafficDownloadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficDownload)) - - ret.Data = map[string]interface{}{ - "sync": sync, - "backup": backup, - "hAssetSize": assetSize, - "hSize": size, - "hTotalSize": totalSize, - "hTrafficUploadSize": hTrafficUploadSize, - "hTrafficDownloadSize": hTrafficDownloadSize, - } -} diff --git a/kernel/api/repo.go b/kernel/api/repo.go index 0d528da42..41bcf21ab 100644 --- a/kernel/api/repo.go +++ b/kernel/api/repo.go @@ -21,11 +21,38 @@ import ( "net/http" "github.com/88250/gulu" + "github.com/dustin/go-humanize" "github.com/gin-gonic/gin" "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/util" ) +func getCloudSpace(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + sync, backup, size, assetSize, totalSize, err := model.GetCloudSpace() + if nil != err { + ret.Code = 1 + ret.Msg = err.Error() + util.PushErrMsg(err.Error(), 3000) + return + } + + hTrafficUploadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficUpload)) + hTrafficDownloadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficDownload)) + + ret.Data = map[string]interface{}{ + "sync": sync, + "backup": backup, + "hAssetSize": assetSize, + "hSize": size, + "hTotalSize": totalSize, + "hTrafficUploadSize": hTrafficUploadSize, + "hTrafficDownloadSize": hTrafficDownloadSize, + } +} + func checkoutRepo(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/router.go b/kernel/api/router.go index 78b533bd6..2ef8f34fa 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -158,13 +158,6 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, getCloudSpace) - ginServer.Handle("POST", "/api/backup/getLocalBackup", model.CheckAuth, getLocalBackup) - ginServer.Handle("POST", "/api/backup/createLocalBackup", model.CheckAuth, model.CheckReadonly, createLocalBackup) - ginServer.Handle("POST", "/api/backup/recoverLocalBackup", model.CheckAuth, model.CheckReadonly, recoverLocalBackup) - ginServer.Handle("POST", "/api/backup/uploadLocalBackup", model.CheckAuth, model.CheckReadonly, uploadLocalBackup) - ginServer.Handle("POST", "/api/backup/downloadCloudBackup", model.CheckAuth, model.CheckReadonly, downloadCloudBackup) - ginServer.Handle("POST", "/api/backup/removeCloudBackup", model.CheckAuth, model.CheckReadonly, removeCloudBackup) - ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, setSyncEnable) ginServer.Handle("POST", "/api/sync/setSyncMode", model.CheckAuth, setSyncMode) ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, setCloudSyncDir) diff --git a/kernel/model/backup.go b/kernel/model/backup.go index fb4d08e3b..6022f3eeb 100644 --- a/kernel/model/backup.go +++ b/kernel/model/backup.go @@ -17,25 +17,10 @@ package model import ( - "bytes" - "crypto/md5" - "crypto/sha256" - "encoding/hex" "errors" "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "strings" - "time" - "github.com/88250/gulu" "github.com/dustin/go-humanize" - "github.com/siyuan-note/encryption" - "github.com/siyuan-note/filelock" - "github.com/siyuan-note/siyuan/kernel/sql" - "github.com/siyuan-note/siyuan/kernel/util" ) type Backup struct { @@ -53,25 +38,6 @@ type Sync struct { SaveDir string `json:"saveDir"` // 本地同步数据存放目录路径 } -func RemoveCloudBackup() (err error) { - err = removeCloudDirPath("backup") - return -} - -func getCloudAvailableBackupSize() (size int64, err error) { - sync, _, assetSize, err := getCloudSpaceOSS() - if nil != err { - return - } - - var syncSize int64 - if nil != sync { - syncSize = int64(sync["size"].(float64)) - } - size = int64(Conf.User.UserSiYuanRepoSize) - syncSize - assetSize - return -} - func GetCloudSpace() (s *Sync, b *Backup, hSize, hAssetSize, hTotalSize string, err error) { sync, backup, assetSize, err := getCloudSpaceOSS() if nil != err { @@ -119,483 +85,3 @@ func byteCountSI(b int64) string { } return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) } - -func GetLocalBackup() (ret *Backup, err error) { - backupDir := Conf.Backup.GetSaveDir() - if err = os.MkdirAll(backupDir, 0755); nil != err { - return - } - - backup, err := os.Stat(backupDir) - ret = &Backup{ - Updated: backup.ModTime().Format("2006-01-02 15:04:05"), - SaveDir: Conf.Backup.GetSaveDir(), - } - return -} - -func RecoverLocalBackup() (err error) { - if "" == Conf.E2EEPasswd { - return errors.New(Conf.Language(11)) - } - - writingDataLock.Lock() - defer writingDataLock.Unlock() - - err = filelock.ReleaseAllFileLocks() - if nil != err { - return - } - sql.WaitForWritingDatabase() - - CloseWatchAssets() - defer WatchAssets() - - // 使用备份恢复时自动暂停同步,避免刚刚恢复后的数据又被同步覆盖 https://github.com/siyuan-note/siyuan/issues/4773 - syncEnabled := Conf.Sync.Enabled - Conf.Sync.Enabled = false - Conf.Save() - - util.PushEndlessProgress(Conf.Language(63)) - util.LogInfof("starting recovery...") - start := time.Now() - data := util.AESDecrypt(Conf.E2EEPasswd) - data, _ = hex.DecodeString(string(data)) - passwd := string(data) - decryptedDataDir, err := decryptDataDir(passwd) - if nil != err { - util.ClearPushProgress(100) - return - } - newDataDir := filepath.Join(util.WorkspaceDir, "data.new") - os.RemoveAll(newDataDir) - if err = os.MkdirAll(newDataDir, 0755); nil != err { - util.ClearPushProgress(100) - return - } - - if err = stableCopy(decryptedDataDir, newDataDir); nil != err { - util.ClearPushProgress(100) - return - } - - oldDataDir := filepath.Join(util.WorkspaceDir, "data.old") - if err = os.RemoveAll(oldDataDir); nil != err { - util.ClearPushProgress(100) - return - } - - // 备份恢复时生成历史 https://github.com/siyuan-note/siyuan/issues/4752 - if gulu.File.IsExist(util.DataDir) { - var historyDir string - historyDir, err = util.GetHistoryDir("backup") - if nil != err { - util.LogErrorf("get history dir failed: %s", err) - util.ClearPushProgress(100) - return - } - - var dirs []os.DirEntry - dirs, err = os.ReadDir(util.DataDir) - if nil != err { - util.LogErrorf("read dir [%s] failed: %s", util.DataDir, err) - util.ClearPushProgress(100) - return - } - for _, dir := range dirs { - from := filepath.Join(util.DataDir, dir.Name()) - to := filepath.Join(historyDir, dir.Name()) - if err = os.Rename(from, to); nil != err { - util.LogErrorf("rename [%s] to [%s] failed: %s", from, to, err) - util.ClearPushProgress(100) - return - } - } - } - - if gulu.File.IsExist(util.DataDir) { - if err = os.RemoveAll(util.DataDir); nil != err { - util.LogErrorf("remove [%s] failed: %s", util.DataDir, err) - util.ClearPushProgress(100) - return - } - } - - if err = os.Rename(newDataDir, util.DataDir); nil != err { - util.ClearPushProgress(100) - util.LogErrorf("rename data dir from [%s] to [%s] failed: %s", newDataDir, util.DataDir, err) - return - } - - elapsed := time.Now().Sub(start).Seconds() - size, _ := util.SizeOfDirectory(util.DataDir, false) - sizeStr := humanize.Bytes(uint64(size)) - util.LogInfof("recovered backup [size=%s] in [%.2fs]", sizeStr, elapsed) - - util.PushEndlessProgress(Conf.Language(62)) - time.Sleep(2 * time.Second) - RefreshFileTree() - if syncEnabled { - func() { - time.Sleep(5 * time.Second) - util.PushMsg(Conf.Language(134), 0) - }() - } - return -} - -func CreateLocalBackup() (err error) { - if "" == Conf.E2EEPasswd { - return errors.New(Conf.Language(11)) - } - - defer util.ClearPushProgress(100) - util.PushEndlessProgress(Conf.Language(22)) - - writingDataLock.Lock() - defer writingDataLock.Unlock() - WaitForWritingFiles() - sql.WaitForWritingDatabase() - err = filelock.ReleaseAllFileLocks() - if nil != err { - return - } - - util.LogInfof("creating backup...") - start := time.Now() - data := util.AESDecrypt(Conf.E2EEPasswd) - data, _ = hex.DecodeString(string(data)) - passwd := string(data) - encryptedDataDir, err := encryptDataDir(passwd) - if nil != err { - util.LogErrorf("encrypt data dir failed: %s", err) - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) - return - } - - newBackupDir := Conf.Backup.GetSaveDir() + ".new" - os.RemoveAll(newBackupDir) - if err = os.MkdirAll(newBackupDir, 0755); nil != err { - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) - return - } - - if err = stableCopy(encryptedDataDir, newBackupDir); nil != err { - util.LogErrorf("copy encrypted data dir from [%s] to [%s] failed: %s", encryptedDataDir, newBackupDir, err) - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) - return - } - - _, err = genCloudIndex(newBackupDir, map[string]bool{}, true) - if nil != err { - return - } - - conf := map[string]interface{}{"updated": time.Now().UnixMilli()} - data, err = gulu.JSON.MarshalJSON(conf) - if nil != err { - util.LogErrorf("marshal backup conf.json failed: %s", err) - } else { - confPath := filepath.Join(newBackupDir, "conf.json") - if err = gulu.File.WriteFileSafer(confPath, data, 0644); nil != err { - util.LogErrorf("write backup conf.json [%s] failed: %s", confPath, err) - } - } - - oldBackupDir := Conf.Backup.GetSaveDir() + ".old" - os.RemoveAll(oldBackupDir) - - backupDir := Conf.Backup.GetSaveDir() - if gulu.File.IsExist(backupDir) { - if err = os.Rename(backupDir, oldBackupDir); nil != err { - util.LogErrorf("rename backup dir from [%s] to [%s] failed: %s", backupDir, oldBackupDir, err) - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) - return - } - } - if err = os.Rename(newBackupDir, backupDir); nil != err { - util.LogErrorf("rename backup dir from [%s] to [%s] failed: %s", newBackupDir, backupDir, err) - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) - return - } - os.RemoveAll(oldBackupDir) - elapsed := time.Now().Sub(start).Seconds() - size, _ := util.SizeOfDirectory(backupDir, false) - sizeStr := humanize.Bytes(uint64(size)) - util.LogInfof("created backup [size=%s] in [%.2fs]", sizeStr, elapsed) - - util.PushEndlessProgress(Conf.Language(21)) - time.Sleep(2 * time.Second) - return -} - -func DownloadBackup() (err error) { - // 使用路径映射文件进行解密验证 https://github.com/siyuan-note/siyuan/issues/3789 - var tmpFetchedFiles int - var tmpTransferSize uint64 - err = ossDownload0(util.TempDir+"/backup", "backup", "/"+pathJSON, &tmpFetchedFiles, &tmpTransferSize, false) - if nil != err { - return - } - data, err := os.ReadFile(filepath.Join(util.TempDir, "/backup/"+pathJSON)) - if nil != err { - return - } - passwdData, _ := hex.DecodeString(string(util.AESDecrypt(Conf.E2EEPasswd))) - passwd := string(passwdData) - data, err = encryption.AESGCMDecryptBinBytes(data, passwd) - if nil != err { - err = errors.New(Conf.Language(28)) - return - } - - localDirPath := Conf.Backup.GetSaveDir() - util.PushEndlessProgress(Conf.Language(68)) - start := time.Now() - fetchedFilesCount, transferSize, _, err := ossDownload(localDirPath, "backup", false) - if nil == err { - elapsed := time.Now().Sub(start).Seconds() - util.LogInfof("downloaded backup [fetchedFiles=%d, transferSize=%s] in [%.2fs]", fetchedFilesCount, humanize.Bytes(transferSize), elapsed) - util.PushEndlessProgress(Conf.Language(69)) - } - return -} - -func UploadBackup() (err error) { - defer util.ClearPushProgress(100) - - if err = checkUploadBackup(); nil != err { - return - } - - localDirPath := Conf.Backup.GetSaveDir() - util.PushEndlessProgress(Conf.Language(61)) - util.LogInfof("uploading backup...") - start := time.Now() - wroteFiles, transferSize, err := ossUpload(true, localDirPath, "backup", "not exist", false) - if nil == err { - elapsed := time.Now().Sub(start).Seconds() - util.LogInfof("uploaded backup [wroteFiles=%d, transferSize=%s] in [%.2fs]", wroteFiles, humanize.Bytes(transferSize), elapsed) - util.PushEndlessProgress(Conf.Language(41)) - time.Sleep(2 * time.Second) - return - } - err = errors.New(formatErrorMsg(err)) - return -} - -var pathJSON = fmt.Sprintf("%x", md5.Sum([]byte("paths.json"))) // 6952277a5a37c17aa6a7c6d86cd507b1 - -func encryptDataDir(passwd string) (encryptedDataDir string, err error) { - encryptedDataDir = filepath.Join(util.TempDir, "incremental", "backup-encrypt") - if err = os.RemoveAll(encryptedDataDir); nil != err { - return - } - if err = os.MkdirAll(encryptedDataDir, 0755); nil != err { - return - } - - ctime := map[string]time.Time{} - metaJSON := map[string]string{} - filepath.Walk(util.DataDir, func(path string, info fs.FileInfo, _ error) error { - if util.DataDir == path { - return nil - } - - if isCloudSkipFile(path, info) { - if info.IsDir() { - return filepath.SkipDir - } - return nil - } - - plainP := strings.TrimPrefix(path, util.DataDir+string(os.PathSeparator)) - p := plainP - parts := strings.Split(p, string(os.PathSeparator)) - buf := bytes.Buffer{} - for i, part := range parts { - buf.WriteString(fmt.Sprintf("%x", sha256.Sum256([]byte(part)))[:7]) - if i < len(parts)-1 { - buf.WriteString(string(os.PathSeparator)) - } - } - p = buf.String() - metaJSON[filepath.ToSlash(p)] = filepath.ToSlash(plainP) - p = encryptedDataDir + string(os.PathSeparator) + p - - if info.IsDir() { - if err = os.MkdirAll(p, 0755); nil != err { - return io.EOF - } - if fi, err0 := os.Stat(path); nil == err0 { - ctime[p] = fi.ModTime() - } - } else { - if err = os.MkdirAll(filepath.Dir(p), 0755); nil != err { - return io.EOF - } - - data, err0 := filelock.NoLockFileRead(path) - if nil != err0 { - util.LogErrorf("read file [%s] failed: %s", path, err0) - err = err0 - return io.EOF - } - data, err0 = encryption.AESGCMEncryptBinBytes(data, passwd) - if nil != err0 { - util.LogErrorf("encrypt file [%s] failed: %s", path, err0) - err = errors.New("encrypt file failed") - return io.EOF - } - - if err0 = gulu.File.WriteFileSafer(p, data, 0644); nil != err0 { - util.LogErrorf("write file [%s] failed: %s", p, err0) - err = err0 - return io.EOF - } - - fi, err0 := os.Stat(path) - if nil != err0 { - util.LogErrorf("stat file [%s] failed: %s", path, err0) - err = err0 - return io.EOF - } - ctime[p] = fi.ModTime() - } - return nil - }) - if nil != err { - return - } - - for p, t := range ctime { - if err = os.Chtimes(p, t, t); nil != err { - return - } - } - - // 检查文件是否全部已经编入索引 - err = filepath.Walk(encryptedDataDir, func(path string, info fs.FileInfo, _ error) error { - if encryptedDataDir == path { - return nil - } - - path = strings.TrimPrefix(path, encryptedDataDir+string(os.PathSeparator)) - path = filepath.ToSlash(path) - if _, ok := metaJSON[path]; !ok { - util.LogErrorf("not found backup path in meta [%s]", path) - return errors.New(Conf.Language(27)) - } - return nil - }) - - if nil != err { - return - } - - data, err := gulu.JSON.MarshalJSON(metaJSON) - if nil != err { - return - } - data, err = encryption.AESGCMEncryptBinBytes(data, passwd) - if nil != err { - return "", errors.New("encrypt file failed") - } - meta := filepath.Join(encryptedDataDir, pathJSON) - if err = gulu.File.WriteFileSafer(meta, data, 0644); nil != err { - return - } - return -} - -func decryptDataDir(passwd string) (decryptedDataDir string, err error) { - decryptedDataDir = filepath.Join(util.TempDir, "incremental", "backup-decrypt") - if err = os.RemoveAll(decryptedDataDir); nil != err { - return - } - - backupDir := Conf.Backup.GetSaveDir() - meta := filepath.Join(util.TempDir, "backup", pathJSON) - data, err := os.ReadFile(meta) - if nil != err { - return - } - data, err = encryption.AESGCMDecryptBinBytes(data, passwd) - if nil != err { - return "", errors.New(Conf.Language(40)) - } - metaJSON := map[string]string{} - if err = gulu.JSON.UnmarshalJSON(data, &metaJSON); nil != err { - return - } - - index := map[string]*CloudIndex{} - data, err = os.ReadFile(filepath.Join(backupDir, "index.json")) - if nil != err { - return - } - if err = gulu.JSON.UnmarshalJSON(data, &index); nil != err { - return - } - - err = filepath.Walk(backupDir, func(path string, info fs.FileInfo, _ error) error { - if backupDir == path || pathJSON == info.Name() || strings.HasSuffix(info.Name(), ".json") { - return nil - } - - encryptedP := strings.TrimPrefix(path, backupDir+string(os.PathSeparator)) - encryptedP = filepath.ToSlash(encryptedP) - decryptedP := metaJSON[encryptedP] - if "" == decryptedP { - if gulu.File.IsDir(path) { - return filepath.SkipDir - } - return nil - } - plainP := filepath.Join(decryptedDataDir, decryptedP) - plainP = filepath.FromSlash(plainP) - - if info.IsDir() { - if err = os.MkdirAll(plainP, 0755); nil != err { - return io.EOF - } - } else { - if err = os.MkdirAll(filepath.Dir(plainP), 0755); nil != err { - return io.EOF - } - - var err0 error - data, err0 = os.ReadFile(path) - if nil != err0 { - util.LogErrorf("read file [%s] failed: %s", path, err0) - err = err0 - return io.EOF - } - data, err0 = encryption.AESGCMDecryptBinBytes(data, passwd) - if nil != err0 { - util.LogErrorf("decrypt file [%s] failed: %s", path, err0) - err = errors.New(Conf.Language(40)) - return io.EOF - } - if err0 = gulu.File.WriteFileSafer(plainP, data, 0644); nil != err0 { - util.LogErrorf("write file [%s] failed: %s", plainP, err0) - err = err0 - return io.EOF - } - - var modTime int64 - idx := index["/"+encryptedP] - if nil == idx { - util.LogErrorf("index file [%s] not found", encryptedP) - modTime = info.ModTime().Unix() - } else { - modTime = idx.Updated - } - if err0 = os.Chtimes(plainP, time.Unix(modTime, 0), time.Unix(modTime, 0)); nil != err0 { - util.LogErrorf("change file [%s] time failed: %s", plainP, err0) - } - } - return nil - }) - return -} diff --git a/kernel/model/box.go b/kernel/model/box.go index c4c7a94bd..329eb9dc9 100644 --- a/kernel/model/box.go +++ b/kernel/model/box.go @@ -349,30 +349,6 @@ func isSkipFile(filename string) bool { return strings.HasPrefix(filename, ".") || "node_modules" == filename || "dist" == filename || "target" == filename } -func checkUploadBackup() (err error) { - if !IsSubscriber() { - if "ios" == util.Container { - return errors.New(Conf.Language(122)) - } - return errors.New(Conf.Language(29)) - } - - backupDir := Conf.Backup.GetSaveDir() - backupSize, err := util.SizeOfDirectory(backupDir, false) - if nil != err { - return - } - - cloudAvailableBackupSize, err := getCloudAvailableBackupSize() - if nil != err { - return - } - if cloudAvailableBackupSize < backupSize { - return errors.New(fmt.Sprintf(Conf.Language(43), byteCountSI(int64(Conf.User.UserSiYuanRepoSize)))) - } - return nil -} - func (box *Box) renameSubTrees(tree *parse.Tree) { subFiles := box.ListFiles(tree.Path) totals := len(subFiles) + 3 diff --git a/kernel/model/export.go b/kernel/model/export.go index df5c2f09e..5d9abd59e 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -120,7 +120,7 @@ func exportData(exportFolder string) (err error) { data := filepath.Join(util.WorkspaceDir, "data") if err = stableCopy(data, exportFolder); nil != err { util.LogErrorf("copy data dir from [%s] to [%s] failed: %s", data, baseFolderName, err) - err = errors.New(fmt.Sprintf(Conf.Language(23), formatErrorMsg(err))) + err = errors.New(fmt.Sprintf(Conf.Language(14), formatErrorMsg(err))) return }