mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 23:20:13 +01:00
🎨 The data history of a database view should follow the doc history and data snapshot https://github.com/siyuan-note/siyuan/issues/9567
This commit is contained in:
parent
4ca62ef701
commit
bf6ff5bc32
5 changed files with 119 additions and 2 deletions
|
|
@ -26,6 +26,46 @@ import (
|
||||||
"github.com/siyuan-note/siyuan/kernel/util"
|
"github.com/siyuan-note/siyuan/kernel/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func renderHistoryAttributeView(c *gin.Context) {
|
||||||
|
ret := gulu.Ret.NewResult()
|
||||||
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
||||||
|
arg, ok := util.JsonArg(c, ret)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := arg["id"].(string)
|
||||||
|
created := arg["created"].(string)
|
||||||
|
view, attrView, err := model.RenderHistoryAttributeView(id, created)
|
||||||
|
if nil != err {
|
||||||
|
ret.Code = -1
|
||||||
|
ret.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var views []map[string]interface{}
|
||||||
|
for _, v := range attrView.Views {
|
||||||
|
view := map[string]interface{}{
|
||||||
|
"id": v.ID,
|
||||||
|
"name": v.Name,
|
||||||
|
"type": v.LayoutType,
|
||||||
|
}
|
||||||
|
|
||||||
|
views = append(views, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Data = map[string]interface{}{
|
||||||
|
"name": attrView.Name,
|
||||||
|
"id": attrView.ID,
|
||||||
|
"viewType": view.GetType(),
|
||||||
|
"viewID": view.GetID(),
|
||||||
|
"views": views,
|
||||||
|
"view": view,
|
||||||
|
"isMirror": av.IsMirror(attrView.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func renderAttributeView(c *gin.Context) {
|
func renderAttributeView(c *gin.Context) {
|
||||||
ret := gulu.Ret.NewResult()
|
ret := gulu.Ret.NewResult()
|
||||||
defer c.JSON(http.StatusOK, ret)
|
defer c.JSON(http.StatusOK, ret)
|
||||||
|
|
|
||||||
|
|
@ -375,6 +375,7 @@ func ServeAPI(ginServer *gin.Engine) {
|
||||||
ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckReadonly, removeSnippet)
|
ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckReadonly, removeSnippet)
|
||||||
|
|
||||||
ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView)
|
ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView)
|
||||||
|
ginServer.Handle("POST", "/api/av/renderHistoryAttributeView", model.CheckAuth, renderHistoryAttributeView)
|
||||||
ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys)
|
ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys)
|
||||||
ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr)
|
ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -181,6 +184,46 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||||
|
createdUnix, parseErr := strconv.ParseInt(created, 10, 64)
|
||||||
|
if nil != parseErr {
|
||||||
|
logging.LogErrorf("parse created [%s] failed: %s", created, parseErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405")
|
||||||
|
globPath := filepath.Join(util.HistoryDir, dirPrefix+"*")
|
||||||
|
matches, err := filepath.Glob(globPath)
|
||||||
|
if nil != err {
|
||||||
|
logging.LogErrorf("glob [%s] failed: %s", globPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 1 > len(matches) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
historyDir := matches[0]
|
||||||
|
avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json")
|
||||||
|
if !gulu.File.IsExist(avJSONPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, readErr := os.ReadFile(avJSONPath)
|
||||||
|
if nil != readErr {
|
||||||
|
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
attrView = &av.AttributeView{}
|
||||||
|
if err = gulu.JSON.UnmarshalJSON(data, attrView); nil != err {
|
||||||
|
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewable, err = renderAttributeView(attrView)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func RenderAttributeView(avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
func RenderAttributeView(avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||||
waitForSyncingStorages()
|
waitForSyncingStorages()
|
||||||
|
|
||||||
|
|
@ -198,12 +241,17 @@ func RenderAttributeView(avID string) (viewable av.Viewable, attrView *av.Attrib
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewable, err = renderAttributeView(attrView)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderAttributeView(attrView *av.AttributeView) (viewable av.Viewable, err error) {
|
||||||
if 1 > len(attrView.Views) {
|
if 1 > len(attrView.Views) {
|
||||||
view := av.NewView()
|
view := av.NewView()
|
||||||
attrView.Views = append(attrView.Views, view)
|
attrView.Views = append(attrView.Views, view)
|
||||||
attrView.ViewID = view.ID
|
attrView.ViewID = view.ID
|
||||||
if err = av.SaveAttributeView(attrView); nil != err {
|
if err = av.SaveAttributeView(attrView); nil != err {
|
||||||
logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
|
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1409,6 +1409,16 @@ func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关联的属性视图也要复制到历史中 https://github.com/siyuan-note/siyuan/issues/9567
|
||||||
|
avNodes := tree.Root.ChildrenByType(ast.NodeAttributeView)
|
||||||
|
for _, avNode := range avNodes {
|
||||||
|
srcAvPath := filepath.Join(util.DataDir, "storage", "av", avNode.AttributeViewID+".json")
|
||||||
|
destAvPath := filepath.Join(historyDir, "storage", "av", avNode.AttributeViewID+".json")
|
||||||
|
if copyErr := filelock.Copy(srcAvPath, destAvPath); nil != copyErr {
|
||||||
|
logging.LogErrorf("copy av [%s] failed: %s", srcAvPath, copyErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
copyDocAssetsToDataAssets(box.ID, p)
|
copyDocAssetsToDataAssets(box.ID, p)
|
||||||
|
|
||||||
removeIDs := treenode.RootChildIDs(tree.ID)
|
removeIDs := treenode.RootChildIDs(tree.ID)
|
||||||
|
|
|
||||||
|
|
@ -464,6 +464,7 @@ func (box *Box) generateDocHistory0() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
luteEngine := util.NewLute()
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
historyPath := filepath.Join(historyDir, box.ID, strings.TrimPrefix(file, filepath.Join(util.DataDir, box.ID)))
|
historyPath := filepath.Join(historyDir, box.ID, strings.TrimPrefix(file, filepath.Join(util.DataDir, box.ID)))
|
||||||
if err = os.MkdirAll(filepath.Dir(historyPath), 0755); nil != err {
|
if err = os.MkdirAll(filepath.Dir(historyPath), 0755); nil != err {
|
||||||
|
|
@ -481,6 +482,23 @@ func (box *Box) generateDocHistory0() {
|
||||||
logging.LogErrorf("generate history failed: %s", err)
|
logging.LogErrorf("generate history failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(file, ".sy") {
|
||||||
|
tree, loadErr := loadTree(file, luteEngine)
|
||||||
|
if nil != loadErr {
|
||||||
|
logging.LogErrorf("load tree [%s] failed: %s", file, loadErr)
|
||||||
|
} else {
|
||||||
|
// 关联的属性视图也要复制到历史中 https://github.com/siyuan-note/siyuan/issues/9567
|
||||||
|
avNodes := tree.Root.ChildrenByType(ast.NodeAttributeView)
|
||||||
|
for _, avNode := range avNodes {
|
||||||
|
srcAvPath := filepath.Join(util.DataDir, "storage", "av", avNode.AttributeViewID+".json")
|
||||||
|
destAvPath := filepath.Join(historyDir, "storage", "av", avNode.AttributeViewID+".json")
|
||||||
|
if copyErr := filelock.Copy(srcAvPath, destAvPath); nil != copyErr {
|
||||||
|
logging.LogErrorf("copy av [%s] failed: %s", srcAvPath, copyErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
|
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
|
||||||
|
|
@ -544,7 +562,7 @@ func (box *Box) recentModifiedDocs() (ret []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.ModTime().After(latestHistoryTime) {
|
if info.ModTime().After(latestHistoryTime) {
|
||||||
ret = append(ret, filepath.Join(path))
|
ret = append(ret, path)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue