🎨 Supports cleaning up unreferenced databases https://github.com/siyuan-note/siyuan/issues/11569

Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
Daniel 2026-01-28 18:42:50 +08:00
parent 10fb34ab88
commit 6c70144d7c
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
2 changed files with 98 additions and 4 deletions

View file

@ -28,6 +28,16 @@ import (
"github.com/siyuan-note/siyuan/kernel/util"
)
func removeUnusedAttributeViews(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
paths := model.RemoveUnusedAttributeViews()
ret.Data = map[string]interface{}{
"paths": paths,
}
}
func getUnusedAttributeViews(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)

View file

@ -29,6 +29,7 @@ import (
"time"
"unicode/utf8"
"github.com/88250/go-humanize"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
@ -45,6 +46,58 @@ import (
"github.com/xrash/smetrics"
)
func RemoveUnusedAttributeViews() (ret []string) {
ret = []string{}
var size int64
msgId := util.PushMsg(Conf.Language(100), 30*1000)
defer func() {
msg := fmt.Sprintf(Conf.Language(91), len(ret), humanize.BytesCustomCeil(uint64(size), 2))
util.PushUpdateMsg(msgId, msg, 7000)
}()
unusedAttributeViews := UnusedAttributeViews()
historyDir, err := GetHistoryDir(HistoryOpClean)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
}
for _, unusedAvID := range unusedAttributeViews {
srcPath := filepath.Join(util.DataDir, "storage", "av", unusedAvID+".json")
if filelock.IsExist(srcPath) {
historyPath := filepath.Join(historyDir, "storage", "av", unusedAvID+".json")
if err = filelock.Copy(srcPath, historyPath); err != nil {
return
}
}
}
for _, unusedAvID := range unusedAttributeViews {
absPath := filepath.Join(util.DataDir, "storage", "av", unusedAvID+".json")
if filelock.IsExist(absPath) {
info, statErr := os.Stat(absPath)
if statErr == nil {
size += info.Size()
}
if removeErr := filelock.RemoveWithoutFatal(absPath); removeErr != nil {
logging.LogErrorf("remove unused av [%s] failed: %s", absPath, removeErr)
util.PushErrMsg(fmt.Sprintf("%s", removeErr), 7000)
return
}
}
ret = append(ret, absPath)
}
if 0 < len(ret) {
IncSync()
}
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
return
}
func UnusedAttributeViews() (ret []string) {
defer logging.Recover()
ret = []string{}
@ -54,7 +107,7 @@ func UnusedAttributeViews() (ret []string) {
return
}
referencedAvIDs := map[string]bool{}
docReferencedAvIDs := map[string]bool{}
luteEngine := util.NewLute()
boxes := Conf.GetBoxes()
for _, box := range boxes {
@ -70,7 +123,7 @@ func UnusedAttributeViews() (ret []string) {
}
for _, tree := range trees {
for _, id := range getAvIDs(tree, allAvIDs) {
referencedAvIDs[id] = true
docReferencedAvIDs[id] = true
}
}
}
@ -78,11 +131,12 @@ func UnusedAttributeViews() (ret []string) {
templateAvIDs := search.FindAllMatchedTargets(filepath.Join(util.DataDir, "templates"), allAvIDs)
for _, id := range templateAvIDs {
referencedAvIDs[id] = true
docReferencedAvIDs[id] = true
}
checkedAvIDs := map[string]bool{}
for _, id := range allAvIDs {
if !referencedAvIDs[id] {
if !docReferencedAvIDs[id] && !isRelatedSrcAvDocReferenced(id, docReferencedAvIDs, checkedAvIDs) {
ret = append(ret, id)
}
}
@ -91,6 +145,36 @@ func UnusedAttributeViews() (ret []string) {
return
}
func isRelatedSrcAvDocReferenced(destAvID string, docReferencedAvIDs, checkedAvIDs map[string]bool) bool {
if checkedAvIDs[destAvID] {
if docReferencedAvIDs[destAvID] {
return true
}
return false
}
checkedAvIDs[destAvID] = true
srcAvIDs := av.GetSrcAvIDs(destAvID)
srcAvIDs = gulu.Str.RemoveElem(srcAvIDs, destAvID) // 忽略自身关联
if 1 > len(srcAvIDs) {
return false
}
for _, srcAvID := range srcAvIDs {
if docReferencedAvIDs[srcAvID] {
return true
}
}
// 递归检查间接关联的 av
for _, srcAvID := range srcAvIDs {
if isRelatedSrcAvDocReferenced(srcAvID, docReferencedAvIDs, checkedAvIDs) {
return true
}
}
return false
}
func getAvIDs(tree *parse.Tree, allAvIDs []string) (ret []string) {
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {