Bladeren bron

:art: The data history of a database view should follow the doc history and data snapshot https://github.com/siyuan-note/siyuan/issues/9567

Daniel 1 jaar geleden
bovenliggende
commit
bf6ff5bc32
5 gewijzigde bestanden met toevoegingen van 119 en 2 verwijderingen
  1. 40 0
      kernel/api/av.go
  2. 1 0
      kernel/api/router.go
  3. 49 1
      kernel/model/attribute_view.go
  4. 10 0
      kernel/model/file.go
  5. 19 1
      kernel/model/history.go

+ 40 - 0
kernel/api/av.go

@@ -26,6 +26,46 @@ import (
 	"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) {
 	ret := gulu.Ret.NewResult()
 	defer c.JSON(http.StatusOK, ret)

+ 1 - 0
kernel/api/router.go

@@ -375,6 +375,7 @@ func ServeAPI(ginServer *gin.Engine) {
 	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/renderHistoryAttributeView", model.CheckAuth, renderHistoryAttributeView)
 	ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys)
 	ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr)
 

+ 49 - 1
kernel/model/attribute_view.go

@@ -18,7 +18,10 @@ package model
 
 import (
 	"bytes"
+	"os"
+	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"text/template"
 	"time"
@@ -181,6 +184,46 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) {
 	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) {
 	waitForSyncingStorages()
 
@@ -198,12 +241,17 @@ func RenderAttributeView(avID string) (viewable av.Viewable, attrView *av.Attrib
 		return
 	}
 
+	viewable, err = renderAttributeView(attrView)
+	return
+}
+
+func renderAttributeView(attrView *av.AttributeView) (viewable av.Viewable, err error) {
 	if 1 > len(attrView.Views) {
 		view := av.NewView()
 		attrView.Views = append(attrView.Views, view)
 		attrView.ViewID = view.ID
 		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
 		}
 	}

+ 10 - 0
kernel/model/file.go

@@ -1409,6 +1409,16 @@ func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
 		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)
 
 	removeIDs := treenode.RootChildIDs(tree.ID)

+ 19 - 1
kernel/model/history.go

@@ -464,6 +464,7 @@ func (box *Box) generateDocHistory0() {
 		return
 	}
 
+	luteEngine := util.NewLute()
 	for _, file := range files {
 		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 {
@@ -481,6 +482,23 @@ func (box *Box) generateDocHistory0() {
 			logging.LogErrorf("generate history failed: %s", err)
 			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())
@@ -544,7 +562,7 @@ func (box *Box) recentModifiedDocs() (ret []string) {
 		}
 
 		if info.ModTime().After(latestHistoryTime) {
-			ret = append(ret, filepath.Join(path))
+			ret = append(ret, path)
 		}
 		return nil
 	})