mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 23:20:13 +01:00
🔥 移除旧版云端同步和备份功能入口 https://github.com/siyuan-note/siyuan/issues/5405
This commit is contained in:
parent
e538824819
commit
2863e32cff
11 changed files with 63 additions and 710 deletions
|
|
@ -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 <a target='_blank' href='https://ld246.com/subscribe/siyuan'>paid subscription</a> (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",
|
||||
|
|
|
|||
|
|
@ -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 <a target='_blank' href='https://ld246.com/subscribe/siyuan'>suscripción de pago</a> (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",
|
||||
|
|
|
|||
|
|
@ -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 <a target='_blank' href='https://ld246.com/subscribe/siyuan'>un abonnement payant</a> (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.",
|
||||
|
|
|
|||
|
|
@ -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": "該功能需要<a target='_blank' href='https://ld246.com/subscribe/siyuan'>付費訂閱</a>(如果你已經訂閱,請在設定-帳號中重繪或者重新登入)",
|
||||
"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": "插入資料檔失敗,請重新打開文檔",
|
||||
|
|
|
|||
|
|
@ -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": "该功能需要<a target='_blank' href='https://ld246.com/subscribe/siyuan'>付费订阅</a>(如果你已经订阅,请在 设置 - 账号中刷新或者重新登录)",
|
||||
"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": "插入资源文件失败,请重新打开文档",
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue