mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 15:10:12 +01:00
🎨 云端同步发生冲突时生成副本 https://github.com/siyuan-note/siyuan/issues/5687
This commit is contained in:
parent
8ee5c07231
commit
581c1b4942
7 changed files with 110 additions and 62 deletions
|
|
@ -29,7 +29,7 @@ require (
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-contrib/sessions v0.0.5
|
github.com/gin-contrib/sessions v0.0.5
|
||||||
github.com/gin-gonic/gin v1.8.1
|
github.com/gin-gonic/gin v1.8.1
|
||||||
github.com/imroc/req/v3 v3.18.0
|
github.com/imroc/req/v3 v3.19.0
|
||||||
github.com/jinzhu/copier v0.3.5
|
github.com/jinzhu/copier v0.3.5
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
github.com/mitchellh/go-ps v1.0.0
|
github.com/mitchellh/go-ps v1.0.0
|
||||||
|
|
@ -38,7 +38,7 @@ require (
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/qiniu/go-sdk/v7 v7.13.0
|
github.com/qiniu/go-sdk/v7 v7.13.0
|
||||||
github.com/radovskyb/watcher v1.0.7
|
github.com/radovskyb/watcher v1.0.7
|
||||||
github.com/siyuan-note/dejavu v0.0.0-20220817100354-86e98bc81dfe
|
github.com/siyuan-note/dejavu v0.0.0-20220821042432-9a0649a3cf06
|
||||||
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75
|
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75
|
||||||
github.com/siyuan-note/eventbus v0.0.0-20220624162334-ca7c06dc771f
|
github.com/siyuan-note/eventbus v0.0.0-20220624162334-ca7c06dc771f
|
||||||
github.com/siyuan-note/filelock v0.0.0-20220720144616-011221f7e128
|
github.com/siyuan-note/filelock v0.0.0-20220720144616-011221f7e128
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,8 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/imroc/req/v3 v3.18.0 h1:IKJOLXnjPG/IzC8bIuTIo77/8/j4VbVFw71D+gh4O1c=
|
github.com/imroc/req/v3 v3.19.0 h1:CgUA54GlgeivADx8nodAFwlgifctXZSxf+V8XibzdDs=
|
||||||
github.com/imroc/req/v3 v3.18.0/go.mod h1:EluRnkfh8A39BmrCARYhcUrfGyR8qPw+O0BZyTy4j9k=
|
github.com/imroc/req/v3 v3.19.0/go.mod h1:EluRnkfh8A39BmrCARYhcUrfGyR8qPw+O0BZyTy4j9k=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
|
|
@ -349,8 +349,8 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
github.com/siyuan-note/dejavu v0.0.0-20220817100354-86e98bc81dfe h1:5n6FyzATQyjrT9DZNVxqCV/CDwkvbTsa3F7icpMrchI=
|
github.com/siyuan-note/dejavu v0.0.0-20220821042432-9a0649a3cf06 h1:v6Sacjqh/Q3IMZvFDCYzXTBPS/fQs5A2pN2SQHXtY88=
|
||||||
github.com/siyuan-note/dejavu v0.0.0-20220817100354-86e98bc81dfe/go.mod h1:8MoQSqxsyMbvMlDgLqs60k7SewDjoYUiJT4h9jeqUJI=
|
github.com/siyuan-note/dejavu v0.0.0-20220821042432-9a0649a3cf06/go.mod h1:/7pAviNPlpJiwZkEg2eyLTEq2/8sfW/AU4eHBvyrHFk=
|
||||||
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75 h1:Bi7/7f29LW+Fm0cHc0J1NO1cZqyJwljSWVmfOqVZgaE=
|
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75 h1:Bi7/7f29LW+Fm0cHc0J1NO1cZqyJwljSWVmfOqVZgaE=
|
||||||
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75/go.mod h1:H8fyqqAbp9XreANjeSbc72zEdFfKTXYN34tc1TjZwtw=
|
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75/go.mod h1:H8fyqqAbp9XreANjeSbc72zEdFfKTXYN34tc1TjZwtw=
|
||||||
github.com/siyuan-note/eventbus v0.0.0-20220624162334-ca7c06dc771f h1:JMobMNZ7AqaKKyEK+WeWFhix/2TDQXgPZDajU00IybU=
|
github.com/siyuan-note/eventbus v0.0.0-20220624162334-ca7c06dc771f h1:JMobMNZ7AqaKKyEK+WeWFhix/2TDQXgPZDajU00IybU=
|
||||||
|
|
|
||||||
|
|
@ -933,58 +933,15 @@ func DuplicateDoc(rootID string) (ret *parse.Tree, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.ID = ast.NewNodeID()
|
resetTree(ret, "Duplicated")
|
||||||
ret.Root.ID = ret.ID
|
createTreeTx(ret)
|
||||||
titleSuffix := "Duplicated"
|
sql.WaitForWritingDatabase()
|
||||||
if t, parseErr := time.Parse("20060102150405", util.TimeFromID(ret.ID)); nil == parseErr {
|
return
|
||||||
titleSuffix = t.Format("2006-01-02 15:04:05")
|
|
||||||
}
|
}
|
||||||
ret.Root.SetIALAttr("id", ret.ID)
|
|
||||||
ret.Root.SetIALAttr("title", ret.Root.IALAttr("title")+" "+titleSuffix)
|
|
||||||
p := path.Join(path.Dir(ret.Path), ret.ID) + ".sy"
|
|
||||||
ret.Path = p
|
|
||||||
ret.HPath = ret.HPath + " " + titleSuffix
|
|
||||||
|
|
||||||
// 收集所有引用
|
func createTreeTx(tree *parse.Tree) {
|
||||||
refIDs := map[string]string{}
|
transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
|
||||||
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
err := PerformTransactions(&[]*Transaction{transaction})
|
||||||
if !entering || ast.NodeBlockRefID != n.Type {
|
|
||||||
return ast.WalkContinue
|
|
||||||
}
|
|
||||||
refIDs[n.TokensStr()] = "1"
|
|
||||||
return ast.WalkContinue
|
|
||||||
})
|
|
||||||
|
|
||||||
// 重置块 ID
|
|
||||||
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
||||||
if !entering || ast.NodeDocument == n.Type {
|
|
||||||
return ast.WalkContinue
|
|
||||||
}
|
|
||||||
if n.IsBlock() && "" != n.ID {
|
|
||||||
newID := ast.NewNodeID()
|
|
||||||
if "1" == refIDs[n.ID] {
|
|
||||||
// 如果是文档自身的内部引用
|
|
||||||
refIDs[n.ID] = newID
|
|
||||||
}
|
|
||||||
n.ID = newID
|
|
||||||
n.SetIALAttr("id", n.ID)
|
|
||||||
}
|
|
||||||
return ast.WalkContinue
|
|
||||||
})
|
|
||||||
|
|
||||||
// 重置内部引用
|
|
||||||
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
||||||
if !entering || ast.NodeBlockRefID != n.Type {
|
|
||||||
return ast.WalkContinue
|
|
||||||
}
|
|
||||||
if "1" != refIDs[n.TokensStr()] {
|
|
||||||
n.Tokens = []byte(refIDs[n.TokensStr()])
|
|
||||||
}
|
|
||||||
return ast.WalkContinue
|
|
||||||
})
|
|
||||||
|
|
||||||
transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: ret}}}
|
|
||||||
err = PerformTransactions(&[]*Transaction{transaction})
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
tx, txErr := sql.BeginTx()
|
tx, txErr := sql.BeginTx()
|
||||||
if nil != txErr {
|
if nil != txErr {
|
||||||
|
|
@ -996,8 +953,6 @@ func DuplicateDoc(rootID string) (ret *parse.Tree, err error) {
|
||||||
logging.LogFatalf("transaction failed: %s", err)
|
logging.LogFatalf("transaction failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sql.WaitForWritingDatabase()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateDocByMd(boxID, p, title, md string, sorts []string) (err error) {
|
func CreateDocByMd(boxID, p, title, md string, sorts []string) (err error) {
|
||||||
|
|
|
||||||
|
|
@ -534,6 +534,40 @@ func syncRepo(boot, exit, byHand bool) (err error) {
|
||||||
logging.LogInfof("synced data repo [ufc=%d, dfc=%d, ucc=%d, dcc=%d, ub=%s, db=%s] in [%.2fs]",
|
logging.LogInfof("synced data repo [ufc=%d, dfc=%d, ucc=%d, dcc=%d, ub=%s, db=%s] in [%.2fs]",
|
||||||
trafficStat.UploadFileCount, trafficStat.DownloadFileCount, trafficStat.UploadChunkCount, trafficStat.DownloadChunkCount, humanize.Bytes(uint64(trafficStat.UploadBytes)), humanize.Bytes(uint64(trafficStat.DownloadBytes)), elapsed.Seconds())
|
trafficStat.UploadFileCount, trafficStat.DownloadFileCount, trafficStat.UploadChunkCount, trafficStat.DownloadChunkCount, humanize.Bytes(uint64(trafficStat.UploadBytes)), humanize.Bytes(uint64(trafficStat.DownloadBytes)), elapsed.Seconds())
|
||||||
|
|
||||||
|
if 0 < len(mergeResult.Conflicts) {
|
||||||
|
// 云端同步发生冲突时生成副本 https://github.com/siyuan-note/siyuan/issues/5687
|
||||||
|
|
||||||
|
luteEngine := NewLute()
|
||||||
|
waitTx := false
|
||||||
|
for _, file := range mergeResult.Conflicts {
|
||||||
|
if !strings.HasSuffix(file.Path, ".sy") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(file.Path[1:], "/")
|
||||||
|
if 2 > len(parts) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
boxID := parts[0]
|
||||||
|
|
||||||
|
absPath := filepath.Join(util.TempDir, "repo", "sync", "conflicts", file.Path)
|
||||||
|
tree, loadTreeErr := loadTree(absPath, luteEngine)
|
||||||
|
if nil != loadTreeErr {
|
||||||
|
logging.LogErrorf("loadd conflicted file [%s] failed: %s", absPath, loadTreeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tree.Box = boxID
|
||||||
|
tree.Path = strings.TrimPrefix(file.Path, "/"+boxID)
|
||||||
|
|
||||||
|
resetTree(tree, "Conflicted")
|
||||||
|
createTreeTx(tree)
|
||||||
|
waitTx = true
|
||||||
|
}
|
||||||
|
if waitTx {
|
||||||
|
sql.WaitForWritingDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if 1 > len(mergeResult.Upserts) && 1 > len(mergeResult.Removes) { // 没有数据变更
|
if 1 > len(mergeResult.Upserts) && 1 > len(mergeResult.Removes) { // 没有数据变更
|
||||||
syncSameCount++
|
syncSameCount++
|
||||||
if 10 < syncSameCount {
|
if 10 < syncSameCount {
|
||||||
|
|
@ -566,7 +600,7 @@ func syncRepo(boot, exit, byHand bool) (err error) {
|
||||||
cache.ClearDocsIAL() // 同步后文档树文档图标没有更新 https://github.com/siyuan-note/siyuan/issues/4939
|
cache.ClearDocsIAL() // 同步后文档树文档图标没有更新 https://github.com/siyuan-note/siyuan/issues/4939
|
||||||
|
|
||||||
fullReindex := 0.2 < float64(len(upserts))/float64(len(indexBeforeSync.Files))
|
fullReindex := 0.2 < float64(len(upserts))/float64(len(indexBeforeSync.Files))
|
||||||
if fullReindex {
|
if fullReindex { // 如果更新的文件比较多则全量重建索引
|
||||||
RefreshFileTree()
|
RefreshFileTree()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,75 @@ package model
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/88250/lute"
|
"github.com/88250/lute"
|
||||||
|
"github.com/88250/lute/ast"
|
||||||
"github.com/88250/lute/parse"
|
"github.com/88250/lute/parse"
|
||||||
"github.com/siyuan-note/filelock"
|
"github.com/siyuan-note/filelock"
|
||||||
"github.com/siyuan-note/logging"
|
"github.com/siyuan-note/logging"
|
||||||
"github.com/siyuan-note/siyuan/kernel/filesys"
|
"github.com/siyuan-note/siyuan/kernel/filesys"
|
||||||
"github.com/siyuan-note/siyuan/kernel/treenode"
|
"github.com/siyuan-note/siyuan/kernel/treenode"
|
||||||
|
"github.com/siyuan-note/siyuan/kernel/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func resetTree(tree *parse.Tree, titleSuffix string) {
|
||||||
|
tree.ID = ast.NewNodeID()
|
||||||
|
tree.Root.ID = tree.ID
|
||||||
|
if t, parseErr := time.Parse("20060102150405", util.TimeFromID(tree.ID)); nil == parseErr {
|
||||||
|
titleSuffix += " " + t.Format("2006-01-02 15:04:05")
|
||||||
|
} else {
|
||||||
|
titleSuffix = "Duplicated " + time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
titleSuffix = "(" + titleSuffix + ")"
|
||||||
|
tree.Root.SetIALAttr("id", tree.ID)
|
||||||
|
tree.Root.SetIALAttr("title", tree.Root.IALAttr("title")+" "+titleSuffix)
|
||||||
|
p := path.Join(path.Dir(tree.Path), tree.ID) + ".sy"
|
||||||
|
tree.Path = p
|
||||||
|
tree.HPath = tree.HPath + " " + titleSuffix
|
||||||
|
|
||||||
|
// 收集所有引用
|
||||||
|
refIDs := map[string]string{}
|
||||||
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||||
|
if !entering || ast.NodeBlockRefID != n.Type {
|
||||||
|
return ast.WalkContinue
|
||||||
|
}
|
||||||
|
refIDs[n.TokensStr()] = "1"
|
||||||
|
return ast.WalkContinue
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重置块 ID
|
||||||
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||||
|
if !entering || ast.NodeDocument == n.Type {
|
||||||
|
return ast.WalkContinue
|
||||||
|
}
|
||||||
|
if n.IsBlock() && "" != n.ID {
|
||||||
|
newID := ast.NewNodeID()
|
||||||
|
if "1" == refIDs[n.ID] {
|
||||||
|
// 如果是文档自身的内部引用
|
||||||
|
refIDs[n.ID] = newID
|
||||||
|
}
|
||||||
|
n.ID = newID
|
||||||
|
n.SetIALAttr("id", n.ID)
|
||||||
|
}
|
||||||
|
return ast.WalkContinue
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重置内部引用
|
||||||
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||||
|
if !entering || ast.NodeBlockRefID != n.Type {
|
||||||
|
return ast.WalkContinue
|
||||||
|
}
|
||||||
|
if "1" != refIDs[n.TokensStr()] {
|
||||||
|
n.Tokens = []byte(refIDs[n.TokensStr()])
|
||||||
|
}
|
||||||
|
return ast.WalkContinue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func pagedPaths(localPath string, pageSize int) (ret map[int][]string) {
|
func pagedPaths(localPath string, pageSize int) (ret map[int][]string) {
|
||||||
ret = map[int][]string{}
|
ret = map[int][]string{}
|
||||||
page := 1
|
page := 1
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// var Mode = "dev"
|
// var Mode = "dev"
|
||||||
//
|
|
||||||
var Mode = "prod"
|
var Mode = "prod"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -267,6 +266,7 @@ func initWorkspaceDir(workspaceArg string) {
|
||||||
if err := os.MkdirAll(osTmpDir, 0755); nil != err {
|
if err := os.MkdirAll(osTmpDir, 0755); nil != err {
|
||||||
log.Fatalf("create os tmp dir [%s] failed: %s", osTmpDir, err)
|
log.Fatalf("create os tmp dir [%s] failed: %s", osTmpDir, err)
|
||||||
}
|
}
|
||||||
|
os.RemoveAll(filepath.Join(TempDir, "repo"))
|
||||||
os.Setenv("TMPDIR", osTmpDir)
|
os.Setenv("TMPDIR", osTmpDir)
|
||||||
os.Setenv("TEMP", osTmpDir)
|
os.Setenv("TEMP", osTmpDir)
|
||||||
os.Setenv("TMP", osTmpDir)
|
os.Setenv("TMP", osTmpDir)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ func BootMobile(container, appDir, workspaceDir, nativeLibDir, privateDataDir, l
|
||||||
osTmpDir := filepath.Join(TempDir, "os")
|
osTmpDir := filepath.Join(TempDir, "os")
|
||||||
os.RemoveAll(osTmpDir)
|
os.RemoveAll(osTmpDir)
|
||||||
os.MkdirAll(osTmpDir, 0755)
|
os.MkdirAll(osTmpDir, 0755)
|
||||||
|
os.RemoveAll(filepath.Join(TempDir, "repo"))
|
||||||
os.Setenv("TMPDIR", osTmpDir)
|
os.Setenv("TMPDIR", osTmpDir)
|
||||||
DBPath = filepath.Join(TempDir, DBName)
|
DBPath = filepath.Join(TempDir, DBName)
|
||||||
BlockTreePath = filepath.Join(TempDir, "blocktree.msgpack")
|
BlockTreePath = filepath.Join(TempDir, "blocktree.msgpack")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue