diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index d7233ffbd..48f8b418c 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,6 @@ { + "snapshotName": "Snapshot Name", + "snapshotNameTip": "Do not include symbols \\ / : * ? " ' < > |", "syncDataRepo": "⚗️ Use data repo sync mechanism", "syncDataRepoTip": "The data repo sync mechanism can better handle data conflicts, and the sync performance is also better", "dataRepo": "Data repo", @@ -910,8 +912,9 @@ "145": "Data repository reset completed", "146": "Failed to reset data repository: %s", "147": "Created a new data snapshot, took %.2fs", - "148": "Checked the data snapshot and found no changes", + "148": "Checked the data snapshot and found no changes, took %.2fs", "149": "Data snapshot has been synchronized, took %.2fs", - "150": "Uploaded/Downloaded files %d/%d\nUploaded/Downloaded chunks %d/%d\nSent/Received bytes %s/%s" + "150": "Uploaded/Downloaded files %d/%d\nUploaded/Downloaded chunks %d/%d\nSent/Received bytes %s/%s", + "151": "Do not include symbols \\ / : * ? " ' < > |" } } diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index d2368eef1..1daaf87bf 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,4 +1,6 @@ { + "snapshotName": "Nombre de la instantánea", + "snapshotNameTip": "No incluir símbolos \\ / : * ? " ' < > |", "syncDataRepo": "⚗️ Usar mecanismo de sincronización de repositorio de datos", "syncDataRepoTip": "El mecanismo de sincronización del repositorio de datos puede manejar mejor los conflictos de datos y el rendimiento de la sincronización también es mejor", "dataRepo": "Repositorio de datos", @@ -910,8 +912,9 @@ "145": "Restablecimiento del repositorio de datos completado", "146": "Fallo en el restablecimiento del repositorio de datos: %s", "147": "Creó una nueva instantánea de datos, tomó %.2fs", - "148": "Se ha comprobado la instantánea de los datos y no se encontro ningún cambio", + "148": "Se ha comprobado la instantánea de los datos y no se encontro ningún cambio, tomó %.2fs", "149": "La instantánea de datos ha sido sincronizada, ha tomado %.2fs", - "150": "Archivos cargados/descargados %d/%d\nFragmentos cargados/descargados %d/%d\nBytes enviados/recibidos %s/%s" + "150": "Archivos cargados/descargados %d/%d\nFragmentos cargados/descargados %d/%d\nBytes enviados/recibidos %s/%s", + "151": "No incluir símbolos \\ / : * ? " ' < > |" } } diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 6252752d8..8377197be 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,6 @@ { + "snapshotName": "Nom de l'instantané", + "snapshotNameTip": "Ne pas inclure les symboles \\ / : * ? " ' < > |", "syncDataRepo": "⚗️ Utiliser le mécanisme de synchronisation du référentiel de données", "syncDataRepoTip": "Le mécanisme de synchronisation du référentiel de données peut mieux gérer les conflits de données, et les performances de synchronisation sont également meilleures", "dataRepo": "Dépôt de données", @@ -910,8 +912,9 @@ "145": "Réinitialisation du référentiel de données terminée", "146": "Échec de la réinitialisation du référentiel de données : %s", "147": "Créé un nouvel instantané de données, a pris %.2fs", - "148": "Vérifié l'instantané des données et n'a trouvé aucun changement", + "148": "Vérifié l'instantané des données et n'a trouvé aucun changement, a pris %.2fs", "149": "L'instantané des données a été synchronisé, a pris %.2fs", - "150": "Fichiers chargés/téléchargés %d/%d\nMorceaux chargés/téléchargés %d/%d\nOctets envoyés/reçus %s/%s" + "150": "Fichiers chargés/téléchargés %d/%d\nMorceaux chargés/téléchargés %d/%d\nOctets envoyés/reçus %s/%s", + "151": "Ne pas inclure les symboles \\ / : * ? " ' < > |" } } diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 81eaaacf1..cd204e70e 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,4 +1,6 @@ { + "snapshotName": "快照名稱", + "snapshotNameTip": "請勿包含符號 \\ / : * ? " ' < > |", "syncDataRepo": "⚗️ 使用數據倉庫同步機制", "syncDataRepoTip": "數據倉庫同步機制能夠更好地處理數據衝突,同步性能也更好", "dataRepo": "數據倉庫", @@ -909,8 +911,9 @@ "145": "數據倉庫重置完畢", "146": "重置數據倉庫失敗:%s", "147": "創建了一個新的數據快照,耗時 %.2fs", - "148": "檢查數據快照,沒有發現任何變化", + "148": "檢查數據快照,沒有發現任何變化,耗時 %.2fs", "149": "已經同步數據快照,耗時 %.2fs", - "150": "上傳/下載文件數 %d/%d\n上傳/下載分塊數 %d/%d\n發送/接受字節數 %s/%s" + "150": "上傳/下載文件數 %d/%d\n上傳/下載分塊數 %d/%d\n發送/接受字節數 %s/%s", + "151": "請勿包含符號 \\ / : * ? " ' < > |" } } diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index ff1184ad9..3f621aa3f 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,6 @@ { + "snapshotName": "快照名称", + "snapshotNameTip": "请勿包含符号 \\ / : * ? " ' < > |", "syncDataRepo": "⚗️ 使用数据仓库同步机制(该特性正处于公测阶段)", "syncDataRepoTip": "数据仓库同步机制能够更好地处理数据冲突,同步性能也更好", "dataRepo": "数据仓库", @@ -911,8 +913,9 @@ "145": "数据仓库重置完毕", "146": "重置数据仓库失败:%s", "147": "创建了一个新的数据快照,耗时 %.2fs", - "148": "检查数据快照,没有发现任何变化", + "148": "检查数据快照,没有发现任何变化,耗时 %.2fs", "149": "已经同步数据快照,耗时 %.2fs", - "150": "上传/下载文件数 %d/%d\n上传/下载分块数 %d/%d\n发送/接受字节数 %s/%s" + "150": "上传/下载文件数 %d/%d\n上传/下载分块数 %d/%d\n发送/接受字节数 %s/%s", + "151": "请勿包含符号 \\ / : * ? " ' < > |" } } diff --git a/app/src/util/history.ts b/app/src/util/history.ts index b1243f904..7b1f4aa00 100644 --- a/app/src/util/history.ts +++ b/app/src/util/history.ts @@ -379,9 +379,9 @@ export const openHistory = () => { break; } else if (type === "genRepo") { const genRepoDialog = new Dialog({ - title: window.siyuan.languages.memo, + title: window.siyuan.languages.snapshotName, content: `
- +
@@ -389,14 +389,14 @@ export const openHistory = () => {
`, width: isMobile() ? "80vw" : "520px", }); - const textAreaElement = genRepoDialog.element.querySelector("textarea"); - textAreaElement.focus(); + const inputElement = genRepoDialog.element.querySelector("input"); + inputElement.focus(); const btnsElement = genRepoDialog.element.querySelectorAll(".b3-button"); btnsElement[0].addEventListener("click", () => { genRepoDialog.destroy(); }); btnsElement[1].addEventListener("click", () => { - fetchPost("/api/repo/indexRepo", {message: textAreaElement.value}, () => { + fetchPost("/api/repo/createSnapshot", {name: inputElement.value}, () => { renderRepo(repoElement, 1); }); genRepoDialog.destroy(); diff --git a/kernel/api/repo.go b/kernel/api/repo.go index b68d7df98..bcb7715b1 100644 --- a/kernel/api/repo.go +++ b/kernel/api/repo.go @@ -66,7 +66,7 @@ func getRepoIndexLogs(c *gin.Context) { } } -func indexRepo(c *gin.Context) { +func createSnapshot(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) @@ -75,8 +75,8 @@ func indexRepo(c *gin.Context) { return } - message := arg["message"].(string) - if err := model.IndexRepo(message); nil != err { + name := arg["name"].(string) + if err := model.CreateSnapshot(name); nil != err { ret.Code = -1 ret.Msg = fmt.Sprintf(model.Conf.Language(140), err) ret.Data = map[string]interface{}{"closeTimeout": 5000} diff --git a/kernel/api/router.go b/kernel/api/router.go index c1f7955a0..0a5c3f4ca 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -254,7 +254,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/repo/initRepoKey", model.CheckAuth, initRepoKey) ginServer.Handle("POST", "/api/repo/resetRepo", model.CheckAuth, resetRepo) ginServer.Handle("POST", "/api/repo/importRepoKey", model.CheckAuth, importRepoKey) - ginServer.Handle("POST", "/api/repo/indexRepo", model.CheckAuth, indexRepo) + ginServer.Handle("POST", "/api/repo/createSnapshot", model.CheckAuth, createSnapshot) ginServer.Handle("POST", "/api/repo/checkoutRepo", model.CheckAuth, checkoutRepo) ginServer.Handle("POST", "/api/repo/getRepoIndexLogs", model.CheckAuth, getRepoIndexLogs) diff --git a/kernel/model/repository.go b/kernel/model/repository.go index d8162d3c6..5d3d86215 100644 --- a/kernel/model/repository.go +++ b/kernel/model/repository.go @@ -85,7 +85,7 @@ func ImportRepoKey(base64Key string) (err error) { time.Sleep(1 * time.Second) util.PushUpdateMsg(msgId, Conf.Language(138), 3000) time.Sleep(1 * time.Second) - if initErr := IndexRepo("Init data repo"); nil != initErr { + if initErr := indexRepo("[Auto] Init data repo"); nil != initErr { util.PushUpdateMsg(msgId, fmt.Sprintf(Conf.Language(140), initErr), 7000) } return @@ -145,7 +145,7 @@ func InitRepoKey() (err error) { time.Sleep(1 * time.Second) util.PushUpdateMsg(msgId, Conf.Language(138), 3000) time.Sleep(1 * time.Second) - if initErr := IndexRepo("Init data repo"); nil != initErr { + if initErr := indexRepo("[Auto] Init data repo"); nil != initErr { util.PushUpdateMsg(msgId, fmt.Sprintf(Conf.Language(140), initErr), 7000) } return @@ -194,7 +194,59 @@ func CheckoutRepo(id string) (err error) { return } -func IndexRepo(memo string) (err error) { +func CreateSnapshot(name string) (err error) { + if 1 > len(Conf.Repo.Key) { + err = errors.New(Conf.Language(26)) + return + } + + name = gulu.Str.RemoveInvisible(name) + if "" == name { + err = errors.New(Conf.Language(142)) + return + } + + if !gulu.File.IsValidFilename(name) { + err = errors.New(Conf.Language(151)) + return + } + + repo, err := newRepository() + if nil != err { + return + } + + util.PushEndlessProgress(Conf.Language(143)) + writingDataLock.Lock() + defer writingDataLock.Unlock() + + start := time.Now() + latest, _ := repo.Latest() + WaitForWritingFiles() + filelock.ReleaseAllFileLocks() + index, err := repo.Index("[Snapshot] "+name, map[string]interface{}{ + CtxPushMsg: CtxPushMsgToStatusBarAndProgress, + }) + if nil != err { + util.PushStatusBar("Create data snapshot failed") + return + } + elapsed := time.Since(start) + + if nil == latest || latest.ID != index.ID { + msg := fmt.Sprintf(Conf.Language(147), elapsed.Seconds()) + util.PushStatusBar(msg) + util.PushMsg(msg, 5000) + } else { + msg := fmt.Sprintf(Conf.Language(148), elapsed.Seconds()) + util.PushStatusBar(msg) + util.PushMsg(msg, 5000) + } + util.PushClearProgress() + return +} + +func indexRepo(memo string) (err error) { if 1 > len(Conf.Repo.Key) { err = errors.New(Conf.Language(26)) return @@ -223,7 +275,7 @@ func IndexRepo(memo string) (err error) { CtxPushMsg: CtxPushMsgToStatusBarAndProgress, }) if nil != err { - util.PushStatusBar("Create data snapshot failed") + util.PushStatusBar("Index data repo failed: " + err.Error()) return } elapsed := time.Since(start) @@ -233,7 +285,7 @@ func IndexRepo(memo string) (err error) { util.PushStatusBar(msg) util.PushMsg(msg, 5000) } else { - msg := Conf.Language(148) + msg := fmt.Sprintf(Conf.Language(148), elapsed.Seconds()) util.PushStatusBar(msg) util.PushMsg(msg, 5000) } @@ -354,23 +406,20 @@ func indexRepoBeforeCloudSync(repo *dejavu.Repo) (err error) { return } elapsed := time.Since(start) - if nil != latest { - if latest.ID != index.ID { - // 对新创建的快照需要更新备注,加入耗时统计 - index.Memo = fmt.Sprintf("[Auto] Cloud sync, completed in %.2fs", elapsed.Seconds()) - err = repo.PutIndex(index) - if nil != err { - util.PushStatusBar("Save data snapshot for cloud sync failed") - util.LogErrorf("put index into data repo before cloud sync failed: %s", err) - return - } - util.PushStatusBar(fmt.Sprintf(Conf.Language(147), elapsed.Seconds())) - } else { - util.PushStatusBar(Conf.Language(148)) + + if nil == latest || latest.ID != index.ID { + // 对新创建的快照需要更新备注,加入耗时统计 + index.Memo = fmt.Sprintf("[Auto] Cloud sync, completed in %.2fs", elapsed.Seconds()) + if err = repo.PutIndex(index); nil != err { + util.PushStatusBar("Save data snapshot for cloud sync failed") + util.LogErrorf("put index into data repo before cloud sync failed: %s", err) + return } - } else { util.PushStatusBar(fmt.Sprintf(Conf.Language(147), elapsed.Seconds())) + } else { + util.PushStatusBar(fmt.Sprintf(Conf.Language(148), elapsed.Seconds())) } + if 7000 < elapsed.Milliseconds() { util.LogWarnf("index data repo before cloud sync elapsed [%dms]", elapsed.Milliseconds()) } diff --git a/kernel/util/websocket.go b/kernel/util/websocket.go index ad2bcf8a1..90cbbd0b4 100644 --- a/kernel/util/websocket.go +++ b/kernel/util/websocket.go @@ -21,6 +21,7 @@ import ( "time" "github.com/88250/gulu" + "github.com/88250/lute/html" "github.com/88250/melody" ) @@ -125,28 +126,33 @@ func ReloadUI() { } func PushTxErr(msg string, code int, data interface{}) { + msg = html.EscapeHTMLStr(msg) BroadcastByType("main", "txerr", code, msg, data) } func PushUpdateMsg(msgId string, msg string, timeout int) { + msg = html.EscapeHTMLStr(msg) BroadcastByType("main", "msg", 0, msg, map[string]interface{}{"id": msgId, "closeTimeout": timeout}) return } func PushMsg(msg string, timeout int) (msgId string) { msgId = gulu.Rand.String(7) + msg = html.EscapeHTMLStr(msg) BroadcastByType("main", "msg", 0, msg, map[string]interface{}{"id": msgId, "closeTimeout": timeout}) return } func PushErrMsg(msg string, timeout int) (msgId string) { msgId = gulu.Rand.String(7) + msg = html.EscapeHTMLStr(msg) BroadcastByType("main", "msg", -1, msg, map[string]interface{}{"id": msgId, "closeTimeout": timeout}) return } func PushStatusBar(msg string) { msg += " (" + time.Now().Format("2006-01-02 15:04:05") + ")" + msg = html.EscapeHTMLStr(msg) BroadcastByType("main", "statusbar", 0, msg, nil) }