浏览代码

:file: 移除文件锁 https://github.com/siyuan-note/siyuan/issues/6010

Liang Ding 2 年之前
父节点
当前提交
69a9713776

+ 2 - 2
app/appearance/langs/en_US.json

@@ -854,8 +854,8 @@
     "72": "Content has been copied to the system clipboard, please go to SiYuan to paste",
     "73": "Importing, please wait...",
     "74": "The kernel has not been fully booted [%d%%], please try again later",
-    "75": "Failed to lock file [%s]",
-    "76": "The data file has been locked by another program. (If a third-party sync disk is used, please check the sync status)",
+    "75": "Attempt to access file failed",
+    "76": "The data file has been locked by another program.",
     "77": "Invalid dir path [%s]",
     "78": "The old and new paths are repeated",
     "79": "Only supports importing Markdown document",

+ 2 - 2
app/appearance/langs/es_ES.json

@@ -854,8 +854,8 @@
     "72": "El contenido se ha copiado en el portapapeles del sistema, por favor vaya a SiYuan para pegar",
     "73": "Importando, por favor espere...",
     "74": "El kernel no ha sido arrancado completamente [%d%%], por favor, inténtelo de nuevo más tarde",
-    "75": "Fallo en el bloqueo del archivo [%s]",
-    "76": "El archivo de datos ha sido bloqueado por otro programa. (Si se utiliza un disco de sincronización de terceros, compruebe el estado de la sincronización)",
+    "75": "Error al intentar acceder al archivo",
+    "76": "El archivo de datos ha sido bloqueado por otro programa.",
     "77": "Ruta inválida [%s]",
     "78": "Los viejos y nuevos caminos se repiten",
     "79": "Sólo admite la importación de documentos Markdown",

+ 2 - 2
app/appearance/langs/fr_FR.json

@@ -854,8 +854,8 @@
     "72": "Le contenu a été copié dans le presse-papiers du système, veuillez vous rendre sur SiYuan pour le coller.",
     "73": "En cours d'importation, veuillez patienter...",
     "74": "Le kernel n'a pas été complètement démarré [%d%%], veuillez réessayer plus tard.",
-    "75": "Impossible de verrouiller le fichier [%s]",
-    "76": "Le fichier de données a été verrouillé par un autre programme. (Si un disque de synchronisation tiers est utilisé, veuillez vérifier le statut de synchronisation).",
+    "75": "La tentative d'accès au fichier a échoué",
+    "76": "Le fichier de données a été verrouillé par un autre programme.",
     "77": "Chemin d'accès invalide [%s]",
     "78": "Les anciens et les nouveaux chemins sont répétés",
     "79": "Prise en charge de l'importation de documents Markdown uniquement",

+ 2 - 2
app/appearance/langs/zh_CHT.json

@@ -854,8 +854,8 @@
     "72": "內容已經複製到系統剪貼簿,請到思源中進行貼上",
     "73": "正在導入,請稍等...",
     "74": "kernel尚未完全啟動 [%d%%],請稍後再試",
-    "75": "嘗試鎖定檔 [%s] 失敗",
-    "76": "資料檔案已被其他程式鎖定。(如果使用了協力廠商同步碟,請檢查同步狀態)",
+    "75": "嘗試訪問資料檔失敗",
+    "76": "資料檔案已被其他程式鎖定。",
     "77": "不可用的目錄路徑 [%s]",
     "78": "新老路徑重複",
     "79": "僅支援導入 Markdown 文檔",

+ 1 - 1
app/appearance/langs/zh_CN.json

@@ -854,7 +854,7 @@
     "72": "内容已经复制到系统剪切板,请到思源中进行粘贴",
     "73": "正在导入,请稍等...",
     "74": "内核尚未完全启动 [%d%%],请稍后再试",
-    "75": "尝试锁定文件 [%s] 失败",
+    "75": "尝试访问文件失败",
     "76": "数据文件已被其他程序锁定。(如果使用了第三方同步盘,请检查同步状态)",
     "77": "不可用的目录路径 [%s]",
     "78": "新老路径重复",

+ 2 - 2
kernel/api/asset.go

@@ -24,7 +24,7 @@ import (
 
 	"github.com/88250/gulu"
 	"github.com/gin-gonic/gin"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
+	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/siyuan/kernel/model"
 	"github.com/siyuan-note/siyuan/kernel/util"
 )
@@ -86,7 +86,7 @@ func setFileAnnotation(c *gin.Context) {
 		ret.Msg = err.Error()
 		return
 	}
-	if err := filesys.WriteFileSafer(writePath, []byte(data)); nil != err {
+	if err := filelock.WriteFile(writePath, []byte(data)); nil != err {
 		ret.Code = -1
 		ret.Msg = err.Error()
 		return

+ 3 - 3
kernel/api/block.go

@@ -112,7 +112,7 @@ func checkBlockExist(c *gin.Context) {
 
 	id := arg["id"].(string)
 	b, err := model.GetBlock(id)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
 		ret.Code = 2
 		ret.Data = id
 		return
@@ -303,7 +303,7 @@ func getBlockInfo(c *gin.Context) {
 
 	id := arg["id"].(string)
 	block, err := model.GetBlock(id)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
 		ret.Code = 2
 		ret.Data = id
 		return
@@ -329,7 +329,7 @@ func getBlockInfo(c *gin.Context) {
 	}
 
 	root, err := model.GetBlock(block.RootID)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
 		ret.Code = 2
 		ret.Data = id
 		return

+ 2 - 2
kernel/api/extension.go

@@ -29,8 +29,8 @@ import (
 	"github.com/88250/gulu"
 	"github.com/88250/lute/ast"
 	"github.com/gin-gonic/gin"
+	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/model"
 	"github.com/siyuan-note/siyuan/kernel/util"
 )
@@ -107,7 +107,7 @@ func extensionCopy(c *gin.Context) {
 		}
 		fName = fName + "-" + ast.NewNodeID() + ext
 		writePath := filepath.Join(assets, fName)
-		if err = filesys.WriteFileSafer(writePath, data); nil != err {
+		if err = filelock.WriteFile(writePath, data); nil != err {
 			ret.Code = -1
 			ret.Msg = err.Error()
 			break

+ 30 - 32
kernel/api/file.go

@@ -17,8 +17,7 @@
 package api
 
 import (
-	"errors"
-	"fmt"
+	"io"
 	"mime/multipart"
 	"net/http"
 	"os"
@@ -70,7 +69,7 @@ func copyFile(c *gin.Context) {
 	}
 
 	dest := arg["dest"].(string)
-	if err = gulu.File.CopyFile(src, dest); nil != err {
+	if err = filelock.Copy(src, dest); nil != err {
 		logging.LogErrorf("copy file [%s] to [%s] failed: %s", src, dest, err)
 		ret.Code = -1
 		ret.Msg = err.Error()
@@ -127,27 +126,40 @@ func putFile(c *gin.Context) {
 			logging.LogErrorf("make a dir [%s] failed: %s", filePath, err)
 		}
 	} else {
-		file, _ := c.FormFile("file")
-		if nil == file {
+		fileHeader, _ := c.FormFile("file")
+		if nil == fileHeader {
 			logging.LogErrorf("form file is nil [path=%s]", filePath)
 			c.Status(400)
 			return
 		}
 
-		dir := filepath.Dir(filePath)
-		if err = os.MkdirAll(dir, 0755); nil != err {
-			logging.LogErrorf("put a file [%s] make dir [%s] failed: %s", filePath, dir, err)
-		} else {
-			if filelock.IsLocked(filePath) {
-				msg := fmt.Sprintf("file [%s] is locked", filePath)
-				logging.LogErrorf(msg)
-				err = errors.New(msg)
-			} else {
-				err = writeFile(file, filePath)
-				if nil != err {
-					logging.LogErrorf("put a file [%s] failed: %s", filePath, err)
-				}
+		for {
+			dir := filepath.Dir(filePath)
+			if err = os.MkdirAll(dir, 0755); nil != err {
+				logging.LogErrorf("put a file [%s] make dir [%s] failed: %s", filePath, dir, err)
+				break
 			}
+
+			var f multipart.File
+			f, err = fileHeader.Open()
+			if nil != err {
+				logging.LogErrorf("open file failed: %s", err)
+				break
+			}
+
+			var data []byte
+			data, err = io.ReadAll(f)
+			if nil != err {
+				logging.LogErrorf("read file failed: %s", err)
+				break
+			}
+
+			err = filelock.WriteFile(filePath, data)
+			if nil != err {
+				logging.LogErrorf("put a file [%s] failed: %s", filePath, err)
+				break
+			}
+			break
 		}
 	}
 	if nil != err {
@@ -172,20 +184,6 @@ func putFile(c *gin.Context) {
 	}
 }
 
-func writeFile(file *multipart.FileHeader, dst string) error {
-	src, err := file.Open()
-	if err != nil {
-		return err
-	}
-	defer src.Close()
-
-	err = gulu.File.WriteFileSaferByReader(dst, src, 0644)
-	if nil != err {
-		return err
-	}
-	return nil
-}
-
 func millisecond2Time(t int64) time.Time {
 	sec := t / 1000
 	msec := t % 1000

+ 3 - 3
kernel/api/filetree.go

@@ -488,10 +488,10 @@ func lockFile(c *gin.Context) {
 	}
 
 	id := arg["id"].(string)
-	locked, filePath := model.LockFileByBlockID(id)
+	locked := model.TryAccessFileByBlockID(id)
 	if !locked {
 		ret.Code = -1
-		ret.Msg = fmt.Sprintf(model.Conf.Language(75), filePath)
+		ret.Msg = fmt.Sprintf(model.Conf.Language(75))
 		ret.Data = map[string]interface{}{"closeTimeout": 5000}
 	}
 }
@@ -635,7 +635,7 @@ func getDoc(c *gin.Context) {
 	}
 
 	blockCount, childBlockCount, content, parentID, parent2ID, rootID, typ, eof, boxID, docPath, err := model.GetDoc(startID, endID, id, index, keyword, mode, size)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
 		ret.Code = 2
 		ret.Data = id
 		return

+ 2 - 1
kernel/api/lute.go

@@ -26,6 +26,7 @@ import (
 	"github.com/88250/lute/parse"
 	"github.com/88250/lute/render"
 	"github.com/gin-gonic/gin"
+	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/model"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -111,7 +112,7 @@ func html2BlockDOM(c *gin.Context) {
 			name = name[0 : len(name)-len(ext)]
 			name = name + "-" + ast.NewNodeID() + ext
 			targetPath := filepath.Join(util.DataDir, "assets", name)
-			if err = gulu.File.CopyFile(localPath, targetPath); nil != err {
+			if err = filelock.Copy(localPath, targetPath); nil != err {
 				logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", localPath, targetPath, err)
 				return ast.WalkStop
 			}

+ 1 - 1
kernel/api/transaction.go

@@ -73,7 +73,7 @@ func performTransactions(c *gin.Context) {
 		err = model.PerformTransactions(&transactions)
 	}
 
-	if errors.Is(err, filelock.ErrUnableLockFile) {
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
 		ret.Code = 1
 		return
 	}

+ 2 - 2
kernel/bazaar/package.go

@@ -30,9 +30,9 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/araddon/dateparse"
 	"github.com/imroc/req/v3"
+	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/httpclient"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	textUnicode "golang.org/x/text/encoding/unicode"
 	"golang.org/x/text/transform"
@@ -375,7 +375,7 @@ func installPackage(data []byte, installPath string) (err error) {
 		}
 	}
 	srcPath := filepath.Join(unzipPath, dir)
-	if err = filesys.Copy(srcPath, installPath); nil != err {
+	if err = filelock.Copy(srcPath, installPath); nil != err {
 		return
 	}
 	return

+ 0 - 67
kernel/filesys/io.go

@@ -1,67 +0,0 @@
-package filesys
-
-import (
-	"io"
-	"os"
-	"sync"
-
-	"github.com/88250/gulu"
-	"github.com/siyuan-note/filelock"
-	"github.com/siyuan-note/logging"
-)
-
-var writingFileLock = sync.Mutex{}
-
-func LockWriteFile() {
-	writingFileLock.Lock()
-}
-
-func UnlockWriteFile() {
-	writingFileLock.Unlock()
-}
-
-func WriteFileSaferByReader(writePath string, reader io.Reader) (err error) {
-	writingFileLock.Lock()
-	defer writingFileLock.Unlock()
-
-	if err = gulu.File.WriteFileSaferByReader(writePath, reader, 0644); nil != err {
-		logging.LogErrorf("write file [%s] failed: %s", writePath, err)
-		return
-	}
-	return
-}
-
-func WriteFileSafer(writePath string, data []byte) (err error) {
-	writingFileLock.Lock()
-	defer writingFileLock.Unlock()
-
-	if err = gulu.File.WriteFileSafer(writePath, data, 0644); nil != err {
-		logging.LogErrorf("write file [%s] failed: %s", writePath, err)
-		return
-	}
-	return
-}
-
-func Copy(source, dest string) (err error) {
-	writingFileLock.Lock()
-	defer writingFileLock.Unlock()
-
-	filelock.ReleaseFileLocks(source)
-	if err = gulu.File.Copy(source, dest); nil != err {
-		logging.LogErrorf("copy [%s] to [%s] failed: %s", source, dest, err)
-		return
-	}
-	return
-}
-
-func RemoveAll(p string) (err error) {
-	writingFileLock.Lock()
-	defer writingFileLock.Unlock()
-
-	filelock.ReleaseFileLocks(p)
-	if err = os.RemoveAll(p); nil != err {
-		logging.LogErrorf("remove all [%s] failed: %s", p, err)
-		return
-	}
-	return
-}

+ 10 - 6
kernel/filesys/tree.go

@@ -38,7 +38,7 @@ import (
 
 func LoadTree(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, err error) {
 	filePath := filepath.Join(util.DataDir, boxID, p)
-	data, err := filelock.LockFileRead(filePath)
+	data, err := filelock.ReadFile(filePath)
 	if nil != err {
 		return
 	}
@@ -72,7 +72,7 @@ func LoadTree(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, err erro
 		}
 		parentPath += ".sy"
 		parentPath = filepath.Join(util.DataDir, boxID, parentPath)
-		parentData, readErr := filelock.LockFileRead(parentPath)
+		parentData, readErr := filelock.ReadFile(parentPath)
 		if nil != readErr {
 			logging.LogWarnf("read parent tree data [%s] failed: %s", parentPath, readErr)
 			hPathBuilder.WriteString("Untitled/")
@@ -124,7 +124,11 @@ func WriteTree(tree *parse.Tree) (err error) {
 		return
 	}
 
-	if err = filelock.LockFileWrite(filePath, output); nil != err {
+	if err = filelock.WriteFile(filePath, output); nil != err {
+		if errors.Is(err, filelock.ErrUnableAccessFile) {
+			return
+		}
+
 		msg := fmt.Sprintf("write data [%s] failed: %s", filePath, err)
 		logging.LogErrorf(msg)
 		return errors.New(msg)
@@ -155,12 +159,12 @@ func recoverParseJSON2Tree(boxID, p, filePath string, luteEngine *lute.Lute) (re
 		return
 	}
 
-	data, err := filelock.NoLockFileRead(tmp)
+	data, err := filelock.ReadFile(tmp)
 	if nil != err {
 		logging.LogErrorf("recover tree read from tmp [%s] failed: %s", tmp, err)
 		return
 	}
-	if err = filelock.NoLockFileWrite(filePath, data); nil != err {
+	if err = filelock.WriteFile(filePath, data); nil != err {
 		logging.LogErrorf("recover tree write [%s] from tmp [%s] failed: %s", filePath, tmp, err)
 		return
 	}
@@ -209,7 +213,7 @@ func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (re
 		if err = os.MkdirAll(filepath.Dir(filePath), 0755); nil != err {
 			return
 		}
-		if err = filelock.LockFileWrite(filePath, output); nil != err {
+		if err = filelock.WriteFile(filePath, output); nil != err {
 			msg := fmt.Sprintf("write data [%s] failed: %s", filePath, err)
 			logging.LogErrorf(msg)
 		}

+ 6 - 7
kernel/go.mod

@@ -5,8 +5,7 @@ go 1.18
 require (
 	github.com/88250/clipboard v0.1.5
 	github.com/88250/css v0.1.2
-	github.com/88250/flock v0.8.2
-	github.com/88250/gulu v1.2.3-0.20220916075322-eb117059d70a
+	github.com/88250/gulu v1.2.3-0.20220929123404-da1dc91c9343
 	github.com/88250/lute v1.7.5-0.20220928025238-bda91cbd4072
 	github.com/88250/pdfcpu v0.3.13
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
@@ -38,10 +37,10 @@ require (
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/qiniu/go-sdk/v7 v7.13.0
 	github.com/radovskyb/watcher v1.0.7
-	github.com/siyuan-note/dejavu v0.0.0-20220916075732-b2e0f1313c75
+	github.com/siyuan-note/dejavu v0.0.0-20220929134953-d072457b8eb0
 	github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75
 	github.com/siyuan-note/eventbus v0.0.0-20220916025349-3ac6e75522da
-	github.com/siyuan-note/filelock v0.0.0-20220720144616-011221f7e128
+	github.com/siyuan-note/filelock v0.0.0-20220929134814-c00908c4f281
 	github.com/siyuan-note/httpclient v0.0.0-20220928030253-4f6a778563e9
 	github.com/siyuan-note/logging v0.0.0-20220717040626-f796b05ee520
 	github.com/steambap/captcha v1.4.1
@@ -86,7 +85,7 @@ require (
 	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/juju/errors v1.0.0 // indirect
-	github.com/klauspost/compress v1.15.10 // indirect
+	github.com/klauspost/compress v1.15.11 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/lucas-clemente/quic-go v0.29.1 // indirect
 	github.com/marten-seemann/qpack v0.2.1 // indirect
@@ -112,11 +111,11 @@ require (
 	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
-	golang.org/x/exp v0.0.0-20220927162542-c76eaa363f9d // indirect
+	golang.org/x/exp v0.0.0-20220929132715-df6207c56b9e // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect
 	golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect
-	golang.org/x/sys v0.0.0-20220927170352-d9d178bc13c6 // indirect
+	golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect

+ 12 - 15
kernel/go.sum

@@ -13,12 +13,10 @@ github.com/88250/clipboard v0.1.5 h1:V/mCiSrjwmIiJwvchGTs+W2ozdINxk7y7KgHNTSzlCI
 github.com/88250/clipboard v0.1.5/go.mod h1:bNLJx4L8cF6fEgiXMPVrK1Iidnaff8BTkktTNtefcks=
 github.com/88250/css v0.1.2 h1:+AADhEwWoGZFbUjqIsBcdnq2xfj8fDFDAGRXhBUhUY8=
 github.com/88250/css v0.1.2/go.mod h1:XfcZHQ0YuUb9VncVBurQfVyw1ZQicsB5Gc9N7BK3/ig=
-github.com/88250/flock v0.8.2 h1:LLbRJw3hoYfjD4g7DiYsYcTCCFTxm8icn/WepLlxIg0=
-github.com/88250/flock v0.8.2/go.mod h1:k+PZxETAUe4vLZx3R39ykvQCIlwHhc7AI2P2NUQV6zw=
 github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5 h1:8HdZozCsXSiEXYAo8Zbi/r2Ld6Dd4MmGHgir3EaSuHQ=
 github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/88250/gulu v1.2.3-0.20220916075322-eb117059d70a h1:qQdnk8clbgA+MXtf5bXOTOby32iQYjqMOn6oBIMV/Tk=
-github.com/88250/gulu v1.2.3-0.20220916075322-eb117059d70a/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
+github.com/88250/gulu v1.2.3-0.20220929123404-da1dc91c9343 h1:GJxJRZmA8GkAiU3GswwsSszKqp1/aywhpyFJ+aC7J+k=
+github.com/88250/gulu v1.2.3-0.20220929123404-da1dc91c9343/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
 github.com/88250/lute v1.7.5-0.20220928025238-bda91cbd4072 h1:0d7YXGtw2ybeGs6oClIFiKvTqySfJCu5SUdJJWil6MA=
 github.com/88250/lute v1.7.5-0.20220928025238-bda91cbd4072/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
 github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
@@ -123,7 +121,6 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
 github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
@@ -214,8 +211,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE
 github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo=
-github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
+github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
+github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -348,14 +345,14 @@ 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/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/siyuan-note/dejavu v0.0.0-20220916075732-b2e0f1313c75 h1:o6wPfHwfPVN0mF+cz8Z5nUFRBcNR0HYuSnOUzFN4LtI=
-github.com/siyuan-note/dejavu v0.0.0-20220916075732-b2e0f1313c75/go.mod h1:b+QmK+87RPSakV5It9oLEFbK0jskmIQumlyqmi7XhVo=
+github.com/siyuan-note/dejavu v0.0.0-20220929134953-d072457b8eb0 h1:28DheQk40ZkTztYoAlOdxP+JIiwtrRCezNqY1QRnOFc=
+github.com/siyuan-note/dejavu v0.0.0-20220929134953-d072457b8eb0/go.mod h1:Y+bg0j0Z7C66oJWPPYMs+wZuFeiTRWhh2aLkqctJFKk=
 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/eventbus v0.0.0-20220916025349-3ac6e75522da h1:/jNhl7LC+9BhkWvNxuJDdsNfA/2wvfuj9mqWx4CbV90=
 github.com/siyuan-note/eventbus v0.0.0-20220916025349-3ac6e75522da/go.mod h1:Sqo4FYX5lAXu7gWkbEdJF0e6P57tNNVV4WDKYDctokI=
-github.com/siyuan-note/filelock v0.0.0-20220720144616-011221f7e128 h1:p90RgCJ0BFRY60wOFQ+XHAgI6ey3sUWbqHcQ7T3KWfQ=
-github.com/siyuan-note/filelock v0.0.0-20220720144616-011221f7e128/go.mod h1:NeC98UUl+U62YScI0qWIjzrK+L4Z6WsBL5bueShjBKs=
+github.com/siyuan-note/filelock v0.0.0-20220929134814-c00908c4f281 h1:9IiKGJm+MaoZkoOCeHYiptfdg14O1kVW3gtZzt5ScVE=
+github.com/siyuan-note/filelock v0.0.0-20220929134814-c00908c4f281/go.mod h1:JkoH0JiM865PMbhzRxddkmsnu+yFjrjZPUOkrBBF7Jk=
 github.com/siyuan-note/httpclient v0.0.0-20220928030253-4f6a778563e9 h1:3fa7E/3isQ+22RQfHctL65sUOFbBspYBq2wiHZPtyCY=
 github.com/siyuan-note/httpclient v0.0.0-20220928030253-4f6a778563e9/go.mod h1:fa1KsHyCuOedk1CKXDi7Y6USwwU5oNo1Zd4jDo3BpDM=
 github.com/siyuan-note/logging v0.0.0-20220717040626-f796b05ee520 h1:kscYjMt7jXYdd7Qj2OSUoBnoHc5B0U/E6OSx86VRLr4=
@@ -424,8 +421,8 @@ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCzt
 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
-golang.org/x/exp v0.0.0-20220927162542-c76eaa363f9d h1:3wgmvnqHUJ8SxiNWwea5NCzTwAVfhTtuV+0ClVFlClc=
-golang.org/x/exp v0.0.0-20220927162542-c76eaa363f9d/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20220929132715-df6207c56b9e h1:CEsRTv4qpqnMsjX0sa7g7EarLEN7mA1LYcQK44QRno8=
+golang.org/x/exp v0.0.0-20220929132715-df6207c56b9e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -514,8 +511,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220927170352-d9d178bc13c6 h1:cy1ko5847T/lJ45eyg/7uLprIE/amW5IXxGtEnQdYMI=
-golang.org/x/sys v0.0.0-20220927170352-d9d178bc13c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20180302201248-b7ef84aaf62a/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 6 - 9
kernel/model/assets.go

@@ -39,7 +39,6 @@ import (
 	"github.com/siyuan-note/httpclient"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/cache"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
@@ -142,7 +141,7 @@ func NetImg2LocalAssets(rootID string) (err error) {
 				name = util.FilterFileName(name)
 				name = "net-img-" + name + "-" + ast.NewNodeID() + ext
 				writePath := filepath.Join(util.DataDir, "assets", name)
-				if err = filesys.WriteFileSafer(writePath, data); nil != err {
+				if err = filelock.WriteFile(writePath, data); nil != err {
 					logging.LogErrorf("write downloaded net img [%s] to local assets [%s] failed: %s", u, writePath, err)
 					return ast.WalkSkipChildren
 				}
@@ -384,7 +383,7 @@ func saveWorkspaceAssets(assets []string) {
 		logging.LogErrorf("create assets conf failed: %s", err)
 		return
 	}
-	if err = filesys.WriteFileSafer(confPath, data); nil != err {
+	if err = filelock.WriteFile(confPath, data); nil != err {
 		logging.LogErrorf("write assets conf failed: %s", err)
 		return
 	}
@@ -479,7 +478,7 @@ func RenameAsset(oldPath, newName string) (err error) {
 
 	newName = util.AssetName(newName) + filepath.Ext(oldPath)
 	newPath := "assets/" + newName
-	if err = filesys.Copy(filepath.Join(util.DataDir, oldPath), filepath.Join(util.DataDir, newPath)); nil != err {
+	if err = filelock.Copy(filepath.Join(util.DataDir, oldPath), filepath.Join(util.DataDir, newPath)); nil != err {
 		logging.LogErrorf("copy asset [%s] failed: %s", oldPath, err)
 		return
 	}
@@ -493,7 +492,7 @@ func RenameAsset(oldPath, newName string) (err error) {
 		pages := pagedPaths(filepath.Join(util.DataDir, notebook.ID), 32)
 		for _, paths := range pages {
 			for _, treeAbsPath := range paths {
-				data, readErr := filelock.NoLockFileRead(treeAbsPath)
+				data, readErr := filelock.ReadFile(treeAbsPath)
 				if nil != readErr {
 					logging.LogErrorf("get data [path=%s] failed: %s", treeAbsPath, readErr)
 					err = readErr
@@ -505,7 +504,7 @@ func RenameAsset(oldPath, newName string) (err error) {
 				}
 
 				data = bytes.Replace(data, []byte(oldName), []byte(newName), -1)
-				if writeErr := filelock.NoLockFileWrite(treeAbsPath, data); nil != writeErr {
+				if writeErr := filelock.WriteFile(treeAbsPath, data); nil != writeErr {
 					logging.LogErrorf("write data [path=%s] failed: %s", treeAbsPath, writeErr)
 					err = writeErr
 					return
@@ -835,8 +834,6 @@ func copyDocAssetsToDataAssets(boxID, parentDocPath string) {
 }
 
 func copyAssetsToDataAssets(rootPath string) {
-	filelock.ReleaseFileLocks(rootPath)
-
 	var assetsDirPaths []string
 	filepath.Walk(rootPath, func(path string, info fs.FileInfo, err error) error {
 		if rootPath == path || nil == info {
@@ -861,7 +858,7 @@ func copyAssetsToDataAssets(rootPath string) {
 
 	dataAssetsPath := filepath.Join(util.DataDir, "assets")
 	for _, assetsDirPath := range assetsDirPaths {
-		if err := gulu.File.Copy(assetsDirPath, dataAssetsPath); nil != err {
+		if err := filelock.Copy(assetsDirPath, dataAssetsPath); nil != err {
 			logging.LogErrorf("copy tree assets from [%s] to [%s] failed: %s", assetsDirPaths, dataAssetsPath, err)
 		}
 	}

+ 22 - 22
kernel/model/box.go

@@ -35,7 +35,6 @@ import (
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/conf"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -87,24 +86,23 @@ func ListNotebooks() (ret []*Box, err error) {
 		boxDirPath := filepath.Join(util.DataDir, dir.Name())
 		boxConfPath := filepath.Join(boxDirPath, ".siyuan", "conf.json")
 		if !gulu.File.IsExist(boxConfPath) {
-			filelock.ReleaseAllFileLocks()
 			if IsUserGuide(dir.Name()) {
-				os.RemoveAll(boxDirPath)
+				filelock.Remove(boxDirPath)
 				continue
 			}
 			to := filepath.Join(util.WorkspaceDir, "corrupted", time.Now().Format("2006-01-02-150405"), dir.Name())
-			if copyErr := gulu.File.CopyDir(boxDirPath, to); nil != copyErr {
+			if copyErr := filelock.Copy(boxDirPath, to); nil != copyErr {
 				logging.LogErrorf("copy corrupted box [%s] failed: %s", boxDirPath, copyErr)
 				continue
 			}
-			if removeErr := os.RemoveAll(boxDirPath); nil != removeErr {
+			if removeErr := filelock.Remove(boxDirPath); nil != removeErr {
 				logging.LogErrorf("remove corrupted box [%s] failed: %s", boxDirPath, removeErr)
 				continue
 			}
 			logging.LogWarnf("moved corrupted box [%s] to [%s]", boxDirPath, to)
 			continue
 		} else {
-			data, readErr := filelock.NoLockFileRead(boxConfPath)
+			data, readErr := filelock.ReadFile(boxConfPath)
 			if nil != readErr {
 				logging.LogErrorf("read box conf [%s] failed: %s", boxConfPath, readErr)
 				continue
@@ -164,7 +162,7 @@ func (box *Box) GetConf() (ret *conf.BoxConf) {
 		return
 	}
 
-	data, err := filelock.NoLockFileRead(confPath)
+	data, err := filelock.ReadFile(confPath)
 	if nil != err {
 		logging.LogErrorf("read box conf [%s] failed: %s", confPath, err)
 		return
@@ -185,7 +183,7 @@ func (box *Box) SaveConf(conf *conf.BoxConf) {
 		return
 	}
 
-	oldData, err := filelock.NoLockFileRead(confPath)
+	oldData, err := filelock.ReadFile(confPath)
 	if nil != err {
 		box.saveConf0(newData)
 		return
@@ -203,7 +201,7 @@ func (box *Box) saveConf0(data []byte) {
 	if err := os.MkdirAll(filepath.Join(util.DataDir, box.ID, ".siyuan"), 0755); nil != err {
 		logging.LogErrorf("save box conf [%s] failed: %s", confPath, err)
 	}
-	if err := filesys.WriteFileSafer(confPath, data); nil != err {
+	if err := filelock.WriteFile(confPath, data); nil != err {
 		logging.LogErrorf("save box conf [%s] failed: %s", confPath, err)
 	}
 }
@@ -226,16 +224,22 @@ func (box *Box) Ls(p string) (ret []*FileInfo, totals int, err error) {
 	}
 
 	for _, f := range files {
-		if util.IsReservedFilename(f.Name()) {
+		name := f.Name()
+		if util.IsReservedFilename(name) {
+			continue
+		}
+		if strings.HasSuffix(name, ".tmp") {
+			// 移除写入失败时产生的临时文件
+			os.Remove(filepath.Join(util.DataDir, box.ID, p, name))
 			continue
 		}
 
 		totals += 1
 		fi := &FileInfo{}
-		fi.name = f.Name()
+		fi.name = name
 		fi.isdir = f.IsDir()
 		fi.size = f.Size()
-		fPath := path.Join(p, f.Name())
+		fPath := path.Join(p, name)
 		if f.IsDir() {
 			fPath += "/"
 		}
@@ -291,11 +295,8 @@ func (box *Box) Move(oldPath, newPath string) error {
 	boxLocalPath := filepath.Join(util.DataDir, box.ID)
 	fromPath := filepath.Join(boxLocalPath, oldPath)
 	toPath := filepath.Join(boxLocalPath, newPath)
-	filelock.ReleaseFileLocks(fromPath)
 
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-	if err := os.Rename(fromPath, toPath); nil != err {
+	if err := filelock.Move(fromPath, toPath); nil != err {
 		msg := fmt.Sprintf(Conf.Language(5), box.Name, fromPath, err)
 		logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, box.Name, err)
 		return errors.New(msg)
@@ -304,7 +305,7 @@ func (box *Box) Move(oldPath, newPath string) error {
 	if oldDir := path.Dir(oldPath); util.IsIDPattern(path.Base(oldDir)) {
 		fromDir := filepath.Join(boxLocalPath, oldDir)
 		if util.IsEmptyDir(fromDir) {
-			os.Remove(fromDir)
+			filelock.Remove(fromDir)
 		}
 	}
 	IncSync()
@@ -314,7 +315,7 @@ func (box *Box) Move(oldPath, newPath string) error {
 func (box *Box) Remove(path string) error {
 	boxLocalPath := filepath.Join(util.DataDir, box.ID)
 	filePath := filepath.Join(boxLocalPath, path)
-	if err := filesys.RemoveAll(filePath); nil != err {
+	if err := filelock.Remove(filePath); nil != err {
 		msg := fmt.Sprintf(Conf.Language(7), box.Name, path, err)
 		logging.LogErrorf("remove [path=%s] in box [%s] failed: %s", path, box.ID, err)
 		return errors.New(msg)
@@ -331,7 +332,6 @@ func (box *Box) Unindex() {
 	sql.RemoveBoxHash(tx, box.ID)
 	sql.DeleteByBoxTx(tx, box.ID)
 	sql.CommitTx(tx)
-	filelock.ReleaseFileLocks(filepath.Join(util.DataDir, box.ID))
 	treenode.RemoveBlockTreesByBoxID(box.ID)
 }
 
@@ -533,7 +533,7 @@ func (box *Box) UpdateHistoryGenerated() {
 	boxLatestHistoryTime[box.ID] = time.Now()
 }
 
-func LockFileByBlockID(id string) (locked bool, filePath string) {
+func TryAccessFileByBlockID(id string) (ok bool) {
 	bt := treenode.GetBlockTree(id)
 	if nil == bt {
 		return
@@ -541,7 +541,7 @@ func LockFileByBlockID(id string) (locked bool, filePath string) {
 	p := filepath.Join(util.DataDir, bt.BoxID, bt.Path)
 
 	if !gulu.File.IsExist(p) {
-		return true, ""
+		return false
 	}
-	return nil == filelock.LockFile(p), p
+	return true
 }

+ 2 - 3
kernel/model/conf.go

@@ -362,7 +362,6 @@ func Close(force bool, execInstallPkg int) (exitCode int) {
 
 	logging.LogInfof("exiting kernel [force=%v, execInstallPkg=%d]", force, execInstallPkg)
 
-	treenode.CloseBlockTree()
 	util.PushMsg(Conf.Language(95), 10000*60)
 	WaitForWritingFiles()
 	if !force {
@@ -435,7 +434,7 @@ func (conf *AppConf) Save() {
 
 	newData, _ := gulu.JSON.MarshalIndentJSON(Conf, "", "  ")
 	confPath := filepath.Join(util.ConfDir, "conf.json")
-	oldData, err := filelock.NoLockFileRead(confPath)
+	oldData, err := filelock.ReadFile(confPath)
 	if nil != err {
 		conf.save0(newData)
 		return
@@ -450,7 +449,7 @@ func (conf *AppConf) Save() {
 
 func (conf *AppConf) save0(data []byte) {
 	confPath := filepath.Join(util.ConfDir, "conf.json")
-	if err := filelock.NoLockFileWrite(confPath, data); nil != err {
+	if err := filelock.WriteFile(confPath, data); nil != err {
 		logging.LogFatalf("write conf [%s] failed: %s", confPath, err)
 	}
 }

+ 5 - 14
kernel/model/export.go

@@ -42,7 +42,6 @@ import (
 	"github.com/emirpasic/gods/stacks/linkedliststack"
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -159,16 +158,8 @@ func exportData(exportFolder string) (err error) {
 		return
 	}
 
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-
-	err = filelock.ReleaseAllFileLocks()
-	if nil != err {
-		return
-	}
-
 	data := filepath.Join(util.WorkspaceDir, "data")
-	if err = stableCopy(data, exportFolder); nil != err {
+	if err = filelock.RoboCopy(data, exportFolder); nil != err {
 		logging.LogErrorf("copy data dir from [%s] to [%s] failed: %s", data, baseFolderName, err)
 		err = errors.New(fmt.Sprintf(Conf.Language(14), formatErrorMsg(err)))
 		return
@@ -224,7 +215,7 @@ func ExportDocx(id, savePath string, removeAssets bool) (err error) {
 	}
 
 	pandoc := exec.Command(Conf.Export.PandocBin, args...)
-	util.CmdAttr(pandoc)
+	gulu.CmdAttr(pandoc)
 	pandoc.Stdin = bytes.NewBufferString(content)
 	output, err := pandoc.CombinedOutput()
 	if nil != err {
@@ -819,7 +810,7 @@ func exportSYZip(boxID, rootDirPath, baseFolderName string, docPaths []string) (
 	// 按文件夹结构复制选择的树
 	for _, tree := range trees {
 		readPath := filepath.Join(util.DataDir, tree.Box, tree.Path)
-		data, readErr := filelock.NoLockFileRead(readPath)
+		data, readErr := filelock.ReadFile(readPath)
 		if nil != readErr {
 			logging.LogErrorf("read file [%s] failed: %s", readPath, readErr)
 			continue
@@ -841,7 +832,7 @@ func exportSYZip(boxID, rootDirPath, baseFolderName string, docPaths []string) (
 	// 引用树放在导出文件夹根路径下
 	for treeID, tree := range refTrees {
 		readPath := filepath.Join(util.DataDir, tree.Box, tree.Path)
-		data, readErr := filelock.NoLockFileRead(readPath)
+		data, readErr := filelock.ReadFile(readPath)
 		if nil != readErr {
 			logging.LogErrorf("read file [%s] failed: %s", readPath, readErr)
 			continue
@@ -903,7 +894,7 @@ func exportSYZip(boxID, rootDirPath, baseFolderName string, docPaths []string) (
 	var sortData []byte
 	var sortErr error
 	if gulu.File.IsExist(sortPath) {
-		sortData, sortErr = filelock.NoLockFileRead(sortPath)
+		sortData, sortErr = filelock.ReadFile(sortPath)
 		if nil != sortErr {
 			logging.LogErrorf("read sort conf failed: %s", sortErr)
 		}

+ 16 - 33
kernel/model/file.go

@@ -134,7 +134,7 @@ func (box *Box) docIAL(p string) (ret map[string]string) {
 
 	filePath := filepath.Join(util.DataDir, box.ID, p)
 
-	data, err := filelock.NoLockFileRead(filePath)
+	data, err := filelock.ReadFile(filePath)
 	if util.IsCorruptedSYData(data) {
 		box.moveCorruptedData(filePath)
 		return nil
@@ -154,14 +154,13 @@ func (box *Box) docIAL(p string) (ret map[string]string) {
 }
 
 func (box *Box) moveCorruptedData(filePath string) {
-	filelock.UnlockFile(filePath)
 	base := filepath.Base(filePath)
 	to := filepath.Join(util.WorkspaceDir, "corrupted", time.Now().Format("2006-01-02-150405"), box.ID, base)
-	if copyErr := gulu.File.CopyFile(filePath, to); nil != copyErr {
+	if copyErr := filelock.Copy(filePath, to); nil != copyErr {
 		logging.LogErrorf("copy corrupted data file [%s] failed: %s", filePath, copyErr)
 		return
 	}
-	if removeErr := os.RemoveAll(filePath); nil != removeErr {
+	if removeErr := filelock.Remove(filePath); nil != removeErr {
 		logging.LogErrorf("remove corrupted data file [%s] failed: %s", filePath, removeErr)
 		return
 	}
@@ -1126,12 +1125,10 @@ func MoveDoc(fromBoxID, fromPath, toBoxID, toPath string) (newPath string, err e
 		} else {
 			absFromPath := filepath.Join(util.DataDir, fromBoxID, fromFolder)
 			absToPath := filepath.Join(util.DataDir, toBoxID, newFolder)
-			filelock.ReleaseFileLocks(absFromPath)
 			if gulu.File.IsExist(absToPath) {
-				filelock.ReleaseFileLocks(absToPath)
-				os.RemoveAll(absToPath)
+				filelock.Remove(absToPath)
 			}
-			if err = os.Rename(absFromPath, absToPath); nil != err {
+			if err = filelock.Move(absFromPath, absToPath); nil != err {
 				msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
 				logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBoxID, err)
 				err = errors.New(msg)
@@ -1156,8 +1153,7 @@ func MoveDoc(fromBoxID, fromPath, toBoxID, toPath string) (newPath string, err e
 	} else {
 		absFromPath := filepath.Join(util.DataDir, fromBoxID, fromPath)
 		absToPath := filepath.Join(util.DataDir, toBoxID, newPath)
-		filelock.ReleaseFileLocks(absFromPath)
-		if err = os.Rename(absFromPath, absToPath); nil != err {
+		if err = filelock.Move(absFromPath, absToPath); nil != err {
 			msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
 			logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBoxID, err)
 			err = errors.New(msg)
@@ -1198,7 +1194,7 @@ func RemoveDoc(boxID, p string) (err error) {
 
 	historyPath := filepath.Join(historyDir, boxID, p)
 	absPath := filepath.Join(util.DataDir, boxID, p)
-	if err = filesys.Copy(absPath, historyPath); nil != err {
+	if err = filelock.Copy(absPath, historyPath); nil != err {
 		return errors.New(fmt.Sprintf(Conf.Language(70), box.Name, absPath, err))
 	}
 
@@ -1211,7 +1207,7 @@ func RemoveDoc(boxID, p string) (err error) {
 	if existChildren {
 		absChildrenDir := filepath.Join(util.DataDir, tree.Box, childrenDir)
 		historyPath = filepath.Join(historyDir, tree.Box, childrenDir)
-		if err = gulu.File.Copy(absChildrenDir, historyPath); nil != err {
+		if err = filelock.Copy(absChildrenDir, historyPath); nil != err {
 			return
 		}
 	}
@@ -1456,7 +1452,7 @@ func moveSorts(rootID, fromBox, toBox string) {
 	fromConfPath := filepath.Join(util.DataDir, fromBox, ".siyuan", "sort.json")
 	fromFullSortIDs := map[string]int{}
 	if gulu.File.IsExist(fromConfPath) {
-		data, err := filelock.NoLockFileRead(fromConfPath)
+		data, err := filelock.ReadFile(fromConfPath)
 		if nil != err {
 			logging.LogErrorf("read sort conf failed: %s", err)
 			return
@@ -1473,7 +1469,7 @@ func moveSorts(rootID, fromBox, toBox string) {
 	toConfPath := filepath.Join(util.DataDir, toBox, ".siyuan", "sort.json")
 	toFullSortIDs := map[string]int{}
 	if gulu.File.IsExist(toConfPath) {
-		data, err := filelock.NoLockFileRead(toConfPath)
+		data, err := filelock.ReadFile(toConfPath)
 		if nil != err {
 			logging.LogErrorf("read sort conf failed: %s", err)
 			return
@@ -1494,7 +1490,7 @@ func moveSorts(rootID, fromBox, toBox string) {
 		logging.LogErrorf("marshal sort conf failed: %s", err)
 		return
 	}
-	if err = filelock.NoLockFileWrite(toConfPath, data); nil != err {
+	if err = filelock.WriteFile(toConfPath, data); nil != err {
 		logging.LogErrorf("write sort conf failed: %s", err)
 		return
 	}
@@ -1529,9 +1525,6 @@ func ChangeFileTreeSort(boxID string, paths []string) {
 	}
 
 	WaitForWritingFiles()
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-
 	box := Conf.Box(boxID)
 	sortIDs := map[string]int{}
 	max := 0
@@ -1575,7 +1568,7 @@ func ChangeFileTreeSort(boxID string, paths []string) {
 	fullSortIDs := map[string]int{}
 	var data []byte
 	if gulu.File.IsExist(confPath) {
-		data, err = filelock.NoLockFileRead(confPath)
+		data, err = filelock.ReadFile(confPath)
 		if nil != err {
 			logging.LogErrorf("read sort conf failed: %s", err)
 			return
@@ -1595,7 +1588,7 @@ func ChangeFileTreeSort(boxID string, paths []string) {
 		logging.LogErrorf("marshal sort conf failed: %s", err)
 		return
 	}
-	if err = filelock.NoLockFileWrite(confPath, data); nil != err {
+	if err = filelock.WriteFile(confPath, data); nil != err {
 		logging.LogErrorf("write sort conf failed: %s", err)
 		return
 	}
@@ -1609,7 +1602,7 @@ func (box *Box) fillSort(files *[]*File) {
 		return
 	}
 
-	data, err := filelock.NoLockFileRead(confPath)
+	data, err := filelock.ReadFile(confPath)
 	if nil != err {
 		logging.LogErrorf("read sort conf failed: %s", err)
 		return
@@ -1656,7 +1649,7 @@ func (box *Box) removeSort(rootID, path string) {
 		return
 	}
 
-	data, err := filelock.NoLockFileRead(confPath)
+	data, err := filelock.ReadFile(confPath)
 	if nil != err {
 		logging.LogErrorf("read sort conf failed: %s", err)
 		return
@@ -1677,7 +1670,7 @@ func (box *Box) removeSort(rootID, path string) {
 		logging.LogErrorf("marshal sort conf failed: %s", err)
 		return
 	}
-	if err = filelock.NoLockFileWrite(confPath, data); nil != err {
+	if err = filelock.WriteFile(confPath, data); nil != err {
 		logging.LogErrorf("write sort conf failed: %s", err)
 		return
 	}
@@ -1685,16 +1678,6 @@ func (box *Box) removeSort(rootID, path string) {
 
 func ServeFile(c *gin.Context, filePath string) (err error) {
 	WaitForWritingFiles()
-
-	if filelock.IsLocked(filePath) {
-		if err = filelock.UnlockFile(filePath); nil == err {
-			logging.LogInfof("unlocked file [%s]", filePath)
-		} else {
-			msg := fmt.Sprintf("unlock file [%s] failed: %s", filePath, err)
-			logging.LogErrorf(msg)
-			return errors.New(msg)
-		}
-	}
 	c.File(filePath)
 	return
 }

+ 1 - 1
kernel/model/format.go

@@ -95,7 +95,7 @@ func generateFormatHistory(tree *parse.Tree) {
 	}
 
 	var data []byte
-	if data, err = filelock.NoLockFileRead(filepath.Join(util.DataDir, tree.Box, tree.Path)); err != nil {
+	if data, err = filelock.ReadFile(filepath.Join(util.DataDir, tree.Box, tree.Path)); err != nil {
 		logging.LogErrorf("generate history failed: %s", err)
 		return
 	}

+ 2 - 2
kernel/model/heading.go

@@ -116,9 +116,9 @@ func Doc2Heading(srcID, targetID string, after bool) (srcTreeBox, srcTreePath st
 		if !util.IsEmptyDir(subDir) {
 			err = errors.New(Conf.Language(20))
 			return
-		} else {
-			os.Remove(subDir) // 移除空文件夹不会有副作用
 		}
+
+		os.Remove(subDir) // 移除空文件夹不会有副作用
 	}
 
 	targetTree, _ := loadTreeByBlockID(targetID)

+ 4 - 11
kernel/model/history.go

@@ -37,7 +37,6 @@ import (
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/conf"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
@@ -147,7 +146,7 @@ func GetDocHistoryContent(historyPath, keyword string) (id, rootID, content stri
 		return
 	}
 
-	data, err := filelock.NoLockFileRead(historyPath)
+	data, err := filelock.ReadFile(historyPath)
 	if nil != err {
 		logging.LogErrorf("read file [%s] failed: %s", historyPath, err)
 		return
@@ -234,33 +233,27 @@ func RollbackDocHistory(boxID, historyPath string) (err error) {
 	}
 
 	WaitForWritingFiles()
-	filesys.LockWriteFile()
 
 	srcPath := historyPath
 	var destPath string
 	baseName := filepath.Base(historyPath)
 	id := strings.TrimSuffix(baseName, ".sy")
 
-	filelock.ReleaseFileLocks(filepath.Join(util.DataDir, boxID))
 	workingDoc := treenode.GetBlockTree(id)
 	if nil != workingDoc {
-		if err = os.RemoveAll(filepath.Join(util.DataDir, boxID, workingDoc.Path)); nil != err {
-			filesys.UnlockWriteFile()
+		if err = filelock.Remove(filepath.Join(util.DataDir, boxID, workingDoc.Path)); nil != err {
 			return
 		}
 	}
 
 	destPath, err = getRollbackDockPath(boxID, historyPath)
 	if nil != err {
-		filesys.UnlockWriteFile()
 		return
 	}
 
-	if err = gulu.File.Copy(srcPath, destPath); nil != err {
-		filesys.UnlockWriteFile()
+	if err = filelock.Copy(srcPath, destPath); nil != err {
 		return
 	}
-	filesys.UnlockWriteFile()
 
 	FullReindex()
 	IncSync()
@@ -446,7 +439,7 @@ func (box *Box) generateDocHistory0() {
 		}
 
 		var data []byte
-		if data, err = filelock.NoLockFileRead(file); err != nil {
+		if data, err = filelock.ReadFile(file); err != nil {
 			logging.LogErrorf("generate history failed: %s", err)
 			return
 		}

+ 10 - 19
kernel/model/import.go

@@ -168,7 +168,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 			return
 		}
 		newSyPath := filepath.Join(filepath.Dir(syPath), tree.ID+".sy")
-		if err = os.Rename(syPath, newSyPath); nil != err {
+		if err = filelock.Move(syPath, newSyPath); nil != err {
 			logging.LogErrorf("rename .sy from [%s] to [%s] failed: %s", syPath, newSyPath, err)
 			return
 		}
@@ -181,7 +181,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 	var sortErr error
 	sortPath := filepath.Join(unzipRootPath, ".siyuan", "sort.json")
 	if gulu.File.IsExist(sortPath) {
-		sortData, sortErr = filelock.NoLockFileRead(sortPath)
+		sortData, sortErr = filelock.ReadFile(sortPath)
 		if nil != sortErr {
 			logging.LogErrorf("read import sort conf failed: %s", sortErr)
 		}
@@ -192,7 +192,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 
 		sortPath = filepath.Join(util.DataDir, boxID, ".siyuan", "sort.json")
 		if gulu.File.IsExist(sortPath) {
-			sortData, sortErr = filelock.NoLockFileRead(sortPath)
+			sortData, sortErr = filelock.ReadFile(sortPath)
 			if nil != sortErr {
 				logging.LogErrorf("read box sort conf failed: %s", sortErr)
 			}
@@ -212,7 +212,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 		if nil != sortErr {
 			logging.LogErrorf("marshal box sort conf failed: %s", sortErr)
 		} else {
-			sortErr = filelock.NoLockFileWrite(sortPath, sortData)
+			sortErr = filelock.WriteFile(sortPath, sortData)
 			if nil != sortErr {
 				logging.LogErrorf("write box sort conf failed: %s", sortErr)
 			}
@@ -265,7 +265,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 	})
 	for i, oldPath := range oldPaths {
 		newPath := renamePaths[oldPath]
-		if err = os.Rename(oldPath, newPath); nil != err {
+		if err = filelock.Move(oldPath, newPath); nil != err {
 			logging.LogErrorf("rename path from [%s] to [%s] failed: %s", oldPath, renamePaths[oldPath], err)
 			return errors.New("rename path failed")
 		}
@@ -304,7 +304,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 	for _, assets := range assetsDirs {
 		if gulu.File.IsDir(assets) {
 			dataAssets := filepath.Join(util.DataDir, "assets")
-			if err = filesys.Copy(assets, dataAssets); nil != err {
+			if err = filelock.Copy(assets, dataAssets); nil != err {
 				logging.LogErrorf("copy assets from [%s] to [%s] failed: %s", assets, dataAssets, err)
 				return
 			}
@@ -312,11 +312,6 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 		os.RemoveAll(assets)
 	}
 
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-
-	filelock.ReleaseAllFileLocks()
-
 	var baseTargetPath string
 	if "/" == toPath {
 		baseTargetPath = "/"
@@ -334,7 +329,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 		return
 	}
 
-	if err = stableCopy(unzipRootPath, targetDir); nil != err {
+	if err = filelock.RoboCopy(unzipRootPath, targetDir); nil != err {
 		logging.LogErrorf("copy data dir from [%s] to [%s] failed: %s", unzipRootPath, util.DataDir, err)
 		err = errors.New("copy data failed")
 		return
@@ -376,12 +371,8 @@ func ImportData(zipPath string) (err error) {
 		return errors.New("invalid data.zip")
 	}
 
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-
-	filelock.ReleaseAllFileLocks()
 	tmpDataPath := filepath.Join(unzipPath, dirs[0].Name())
-	if err = stableCopy(tmpDataPath, util.DataDir); nil != err {
+	if err = filelock.RoboCopy(tmpDataPath, util.DataDir); nil != err {
 		logging.LogErrorf("copy data dir from [%s] to [%s] failed: %s", tmpDataPath, util.DataDir, err)
 		err = errors.New("copy data failed")
 		return
@@ -533,7 +524,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
 						name = filepath.Base(fullPath)
 						name = util.AssetName(name)
 						assetTargetPath := filepath.Join(assetDirPath, name)
-						if err = gulu.File.Copy(fullPath, assetTargetPath); nil != err {
+						if err = filelock.Copy(fullPath, assetTargetPath); nil != err {
 							logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", fullPath, assetTargetPath, err)
 							return ast.WalkContinue
 						}
@@ -622,7 +613,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
 				name := filepath.Base(absolutePath)
 				name = util.AssetName(name)
 				assetTargetPath := filepath.Join(assetDirPath, name)
-				if err = gulu.File.CopyFile(absolutePath, assetTargetPath); nil != err {
+				if err = filelock.Copy(absolutePath, assetTargetPath); nil != err {
 					logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", absolutePath, assetTargetPath, err)
 					return ast.WalkContinue
 				}

+ 0 - 4
kernel/model/index.go

@@ -32,7 +32,6 @@ import (
 	"github.com/dustin/go-humanize"
 	"github.com/emirpasic/gods/sets/hashset"
 	"github.com/siyuan-note/eventbus"
-	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/cache"
 	"github.com/siyuan-note/siyuan/kernel/filesys"
@@ -95,7 +94,6 @@ func (box *Box) Index(fullRebuildIndex bool) (treeCount int, treeSize int64) {
 		idHashMap[tree.ID] = tree.Hash
 		if 1 < i && 0 == i%64 {
 			util.PushEndlessProgress(fmt.Sprintf(Conf.Language(88), i, len(files)-i))
-			filelock.ReleaseAllFileLocks()
 		}
 		i++
 	}
@@ -175,7 +173,6 @@ func (box *Box) Index(fullRebuildIndex bool) (treeCount int, treeSize int64) {
 		}
 		if 1 < i && 0 == i%64 {
 			util.PushEndlessProgress(fmt.Sprintf("["+box.Name+"] "+Conf.Language(53), i, treeCount-i))
-			filelock.ReleaseAllFileLocks()
 		}
 		i++
 	}
@@ -314,7 +311,6 @@ func IndexRefs() {
 				}
 				if 1 < i && 0 == i%64 {
 					util.PushEndlessProgress(fmt.Sprintf(Conf.Language(55), i))
-					filelock.ReleaseAllFileLocks()
 				}
 				i++
 			}

+ 4 - 6
kernel/model/mount.go

@@ -29,7 +29,6 @@ import (
 	"github.com/88250/lute/ast"
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
 )
@@ -79,7 +78,6 @@ func RemoveBox(boxID string) (err error) {
 		return errors.New(fmt.Sprintf("can not remove [%s] caused by it is not a dir", boxID))
 	}
 
-	filelock.ReleaseFileLocks(localPath)
 	if !IsUserGuide(boxID) {
 		var historyDir string
 		historyDir, err = GetHistoryDir(HistoryOpDelete)
@@ -89,7 +87,7 @@ func RemoveBox(boxID string) (err error) {
 		}
 		p := strings.TrimPrefix(localPath, util.DataDir)
 		historyPath := filepath.Join(historyDir, p)
-		if err = gulu.File.Copy(localPath, historyPath); nil != err {
+		if err = filelock.Copy(localPath, historyPath); nil != err {
 			logging.LogErrorf("gen sync history failed: %s", err)
 			return
 		}
@@ -98,7 +96,7 @@ func RemoveBox(boxID string) (err error) {
 	}
 
 	unmount0(boxID)
-	if err = filesys.RemoveAll(localPath); nil != err {
+	if err = filelock.Remove(localPath); nil != err {
 		return
 	}
 	IncSync()
@@ -144,12 +142,12 @@ func Mount(boxID string) (alreadyMount bool, err error) {
 			reMountGuide = true
 		}
 
-		if err = os.RemoveAll(localPath); nil != err {
+		if err = filelock.Remove(localPath); nil != err {
 			return
 		}
 
 		p := filepath.Join(util.WorkingDir, "guide", boxID)
-		if err = gulu.File.Copy(p, localPath); nil != err {
+		if err = filelock.Copy(p, localPath); nil != err {
 			return
 		}
 

+ 0 - 6
kernel/model/repository.go

@@ -36,11 +36,9 @@ import (
 	"github.com/siyuan-note/dejavu/entity"
 	"github.com/siyuan-note/encryption"
 	"github.com/siyuan-note/eventbus"
-	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/httpclient"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/cache"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -222,11 +220,8 @@ func CheckoutRepo(id string) (err error) {
 	}
 
 	util.PushEndlessProgress(Conf.Language(63))
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
 	WaitForWritingFiles()
 	sql.WaitForWritingDatabase()
-	filelock.ReleaseAllFileLocks()
 	CloseWatchAssets()
 	defer WatchAssets()
 
@@ -451,7 +446,6 @@ func IndexRepo(memo string) (err error) {
 	start := time.Now()
 	latest, _ := repo.Latest()
 	WaitForWritingFiles()
-	filelock.ReleaseAllFileLocks()
 	index, err := repo.Index(memo, map[string]interface{}{
 		eventbus.CtxPushMsg: eventbus.CtxPushMsgToStatusBarAndProgress,
 	})

+ 0 - 37
kernel/model/sync.go

@@ -20,7 +20,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"strings"
@@ -32,7 +31,6 @@ import (
 	"github.com/dustin/go-humanize"
 	"github.com/siyuan-note/dejavu"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -64,9 +62,6 @@ func SyncData(boot, exit, byHand bool) {
 		return
 	}
 
-	filesys.LockWriteFile()
-	defer filesys.UnlockWriteFile()
-
 	if util.IsMutexLocked(&syncLock) {
 		logging.LogWarnf("sync is in progress")
 		planSyncAfter(30 * time.Second)
@@ -411,38 +406,6 @@ func IncSync() {
 	planSyncAfter(30 * time.Second)
 }
 
-func stableCopy(src, dest string) (err error) {
-	if gulu.OS.IsWindows() {
-		robocopy := "robocopy"
-		cmd := exec.Command(robocopy, src, dest, "/DCOPY:T", "/E", "/IS", "/R:0", "/NFL", "/NDL", "/NJH", "/NJS", "/NP", "/NS", "/NC")
-		util.CmdAttr(cmd)
-		var output []byte
-		output, err = cmd.CombinedOutput()
-		if strings.Contains(err.Error(), "exit status 16") {
-			// 某些版本的 Windows 无法同步 https://github.com/siyuan-note/siyuan/issues/4197
-			return gulu.File.Copy(src, dest)
-		}
-
-		if nil != err && strings.Contains(err.Error(), exec.ErrNotFound.Error()) {
-			robocopy = os.Getenv("SystemRoot") + "\\System32\\" + "robocopy"
-			cmd = exec.Command(robocopy, src, dest, "/DCOPY:T", "/E", "/IS", "/R:0", "/NFL", "/NDL", "/NJH", "/NJS", "/NP", "/NS", "/NC")
-			util.CmdAttr(cmd)
-			output, err = cmd.CombinedOutput()
-		}
-		if nil == err ||
-			strings.Contains(err.Error(), "exit status 3") ||
-			strings.Contains(err.Error(), "exit status 1") ||
-			strings.Contains(err.Error(), "exit status 2") ||
-			strings.Contains(err.Error(), "exit status 5") ||
-			strings.Contains(err.Error(), "exit status 6") ||
-			strings.Contains(err.Error(), "exit status 7") {
-			return nil
-		}
-		logging.LogErrorf("robocopy data from [%s] to [%s] failed: %s %s", src, dest, string(output), err)
-	}
-	return gulu.File.Copy(src, dest)
-}
-
 func planSyncAfter(d time.Duration) {
 	syncPlanTime = time.Now().Add(d)
 }

+ 19 - 16
kernel/model/transaction.go

@@ -20,7 +20,6 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -123,7 +122,7 @@ func flushTx() {
 		case TxErrCodeBlockNotFound:
 			util.PushTxErr("Transaction failed", txErr.code, nil)
 			return
-		case TxErrCodeUnableLockFile:
+		case TxErrCodeUnableAccessFile:
 			util.PushTxErr(Conf.Language(76), txErr.code, txErr.id)
 			return
 		default:
@@ -175,9 +174,9 @@ func PerformTransactions(transactions *[]*Transaction) (err error) {
 }
 
 const (
-	TxErrCodeBlockNotFound  = 0
-	TxErrCodeUnableLockFile = 1
-	TxErrCodeWriteTree      = 2
+	TxErrCodeBlockNotFound    = 0
+	TxErrCodeUnableAccessFile = 1
+	TxErrCodeWriteTree        = 2
 )
 
 type TxErr struct {
@@ -244,6 +243,10 @@ func performTx(tx *Transaction) (ret *TxErr) {
 	}
 
 	if cr := tx.commit(); nil != cr {
+		if errors.Is(cr, filelock.ErrUnableAccessFile) {
+			return &TxErr{code: TxErrCodeUnableAccessFile, msg: cr.Error()}
+		}
+
 		logging.LogErrorf("commit tx failed: %s", cr)
 		return &TxErr{msg: cr.Error()}
 	}
@@ -415,8 +418,8 @@ func (tx *Transaction) doPrependInsert(operation *Operation) (ret *TxErr) {
 		return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID}
 	}
 	tree, err := tx.loadTree(block.ID)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
-		return &TxErr{code: TxErrCodeUnableLockFile, msg: err.Error(), id: block.ID}
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
+		return &TxErr{code: TxErrCodeUnableAccessFile, msg: err.Error(), id: block.ID}
 	}
 	if nil != err {
 		msg := fmt.Sprintf("load tree [id=%s] failed: %s", block.ID, err)
@@ -503,8 +506,8 @@ func (tx *Transaction) doAppendInsert(operation *Operation) (ret *TxErr) {
 		return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID}
 	}
 	tree, err := tx.loadTree(block.ID)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
-		return &TxErr{code: TxErrCodeUnableLockFile, msg: err.Error(), id: block.ID}
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
+		return &TxErr{code: TxErrCodeUnableAccessFile, msg: err.Error(), id: block.ID}
 	}
 	if nil != err {
 		msg := fmt.Sprintf("load tree [id=%s] failed: %s", block.ID, err)
@@ -661,8 +664,8 @@ func (tx *Transaction) doDelete(operation *Operation) (ret *TxErr) {
 	var err error
 	id := operation.ID
 	tree, err := tx.loadTree(id)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
-		return &TxErr{code: TxErrCodeUnableLockFile, msg: err.Error(), id: id}
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
+		return &TxErr{code: TxErrCodeUnableAccessFile, msg: err.Error(), id: id}
 	}
 	if ErrBlockNotFound == err {
 		return nil // move 以后这里会空,算作正常情况
@@ -712,8 +715,8 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
 		}
 	}
 	tree, err := tx.loadTree(block.ID)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
-		return &TxErr{code: TxErrCodeUnableLockFile, msg: err.Error(), id: block.ID}
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
+		return &TxErr{code: TxErrCodeUnableAccessFile, msg: err.Error(), id: block.ID}
 	}
 	if nil != err {
 		msg := fmt.Sprintf("load tree [id=%s] failed: %s", block.ID, err)
@@ -750,7 +753,7 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
 
 				// 只有全局 assets 才移动到相对 assets
 				targetP := filepath.Join(assets, filepath.Base(assetPath))
-				if e = os.Rename(assetPath, targetP); nil != err {
+				if e = filelock.Move(assetPath, targetP); nil != err {
 					logging.LogErrorf("copy path of asset from [%s] to [%s] failed: %s", assetPath, targetP, err)
 					return ast.WalkContinue
 				}
@@ -846,8 +849,8 @@ func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) {
 	id := operation.ID
 
 	tree, err := tx.loadTree(id)
-	if errors.Is(err, filelock.ErrUnableLockFile) {
-		return &TxErr{code: TxErrCodeUnableLockFile, msg: err.Error(), id: id}
+	if errors.Is(err, filelock.ErrUnableAccessFile) {
+		return &TxErr{code: TxErrCodeUnableAccessFile, msg: err.Error(), id: id}
 	}
 	if nil != err {
 		logging.LogErrorf("load tree [id=%s] failed: %s", id, err)

+ 1 - 1
kernel/model/tree.go

@@ -122,7 +122,7 @@ func pagedPaths(localPath string, pageSize int) (ret map[int][]string) {
 }
 
 func loadTree(localPath string, luteEngine *lute.Lute) (ret *parse.Tree, err error) {
-	data, err := filelock.NoLockFileRead(localPath)
+	data, err := filelock.ReadFile(localPath)
 	if nil != err {
 		logging.LogErrorf("get data [path=%s] failed: %s", localPath, err)
 		return

+ 1 - 1
kernel/model/updater.go

@@ -45,7 +45,7 @@ func execNewVerInstallPkg(newVerInstallPkgPath string) {
 	} else if gulu.OS.IsLinux() {
 		cmd = exec.Command("sh", "-c", newVerInstallPkgPath)
 	}
-	util.CmdAttr(cmd)
+	gulu.CmdAttr(cmd)
 	cmdErr := cmd.Start()
 	if nil != cmdErr {
 		logging.LogErrorf("exec install new version failed: %s", cmdErr)

+ 3 - 3
kernel/model/upload.go

@@ -27,8 +27,8 @@ import (
 	"github.com/88250/gulu"
 	"github.com/88250/lute/ast"
 	"github.com/gin-gonic/gin"
+	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -95,7 +95,7 @@ func InsertLocalAssets(id string, assetPaths []string, isUpload bool) (succMap m
 				f.Close()
 				return
 			}
-			if err = filesys.WriteFileSaferByReader(writePath, f); nil != err {
+			if err = filelock.WriteFileByReader(writePath, f); nil != err {
 				f.Close()
 				return
 			}
@@ -180,7 +180,7 @@ func Upload(c *gin.Context) {
 				f.Close()
 				break
 			}
-			if err = filesys.WriteFileSaferByReader(writePath, f); nil != err {
+			if err = filelock.WriteFileByReader(writePath, f); nil != err {
 				errFiles = append(errFiles, fName)
 				ret.Msg = err.Error()
 				f.Close()

+ 10 - 45
kernel/treenode/blocktree.go

@@ -24,7 +24,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/88250/flock"
 	"github.com/88250/gulu"
 	"github.com/88250/lute/ast"
 	"github.com/88250/lute/parse"
@@ -215,8 +214,6 @@ func IndexBlockTree(tree *parse.Tree) {
 	// 新建索引不变更持久化文件,调用处会负责调用 SaveBlockTree()
 }
 
-var blocktreeFileLock *flock.Flock
-
 func AutoFlushBlockTree() {
 	for {
 		if blockTreesChanged {
@@ -230,36 +227,23 @@ func AutoFlushBlockTree() {
 func InitBlockTree(force bool) {
 	start := time.Now()
 
-	if nil == blocktreeFileLock {
-		blocktreeFileLock = flock.New(util.BlockTreePath)
-	} else {
-		if force {
-			err := blocktreeFileLock.Unlock()
-			if nil != err {
-				logging.LogErrorf("unlock blocktree file failed: %s", err)
-			}
-			err = os.RemoveAll(util.BlockTreePath)
-			if nil != err {
-				logging.LogErrorf("remove blocktree file failed: %s", err)
-			}
-			blocktreeFileLock = flock.New(util.BlockTreePath)
-			return
+	if force {
+		err := os.RemoveAll(util.BlockTreePath)
+		if nil != err {
+			logging.LogErrorf("remove blocktree file failed: %s", err)
 		}
+		return
 	}
 
 	var err error
-	if err = blocktreeFileLock.Lock(); nil != err {
-		logging.LogErrorf("read block tree failed: %s", err)
+	fh, err := os.OpenFile(util.BlockTreePath, os.O_RDWR, 0644)
+	if nil != err {
+		logging.LogErrorf("open block tree file failed: %s", err)
 		os.Exit(util.ExitCodeBlockTreeErr)
 		return
 	}
+	defer fh.Close()
 
-	fh := blocktreeFileLock.Fh()
-	if _, err = fh.Seek(0, io.SeekStart); nil != err {
-		logging.LogErrorf("read block tree failed: %s", err)
-		os.Exit(util.ExitCodeBlockTreeErr)
-		return
-	}
 	data, err := io.ReadAll(fh)
 	if nil != err {
 		logging.LogErrorf("read block tree failed: %s", err)
@@ -269,7 +253,6 @@ func InitBlockTree(force bool) {
 	blockTreesLock.Lock()
 	if err = msgpack.Unmarshal(data, &blockTrees); nil != err {
 		logging.LogErrorf("unmarshal block tree failed: %s", err)
-		blocktreeFileLock.Unlock()
 		if err = os.RemoveAll(util.BlockTreePath); nil != err {
 			logging.LogErrorf("removed corrupted block tree failed: %s", err)
 		}
@@ -287,16 +270,6 @@ func InitBlockTree(force bool) {
 func SaveBlockTree() {
 	start := time.Now()
 
-	if nil == blocktreeFileLock {
-		blocktreeFileLock = flock.New(util.BlockTreePath)
-	}
-
-	if err := blocktreeFileLock.Lock(); nil != err {
-		logging.LogErrorf("read block tree failed: %s", err)
-		os.Exit(util.ExitCodeBlockTreeErr)
-		return
-	}
-
 	blockTreesLock.Lock()
 	data, err := msgpack.Marshal(blockTrees)
 	if nil != err {
@@ -306,8 +279,7 @@ func SaveBlockTree() {
 	}
 	blockTreesLock.Unlock()
 
-	fh := blocktreeFileLock.Fh()
-	if err = gulu.File.WriteFileSaferByHandle(fh, data); nil != err {
+	if err = gulu.File.WriteFileSafer(util.BlockTreePath, data, 0644); nil != err {
 		logging.LogErrorf("write block tree failed: %s", err)
 		os.Exit(util.ExitCodeBlockTreeErr)
 		return
@@ -317,10 +289,3 @@ func SaveBlockTree() {
 		logging.LogWarnf("save block tree [size=%s] to [%s], elapsed [%.2fs]", humanize.Bytes(uint64(len(data))), util.BlockTreePath, elapsed)
 	}
 }
-
-func CloseBlockTree() {
-	SaveBlockTree()
-	if err := blocktreeFileLock.Unlock(); nil != err {
-		logging.LogErrorf("close block tree failed: %s", err)
-	}
-}

+ 0 - 24
kernel/util/cmdattr.go

@@ -1,24 +0,0 @@
-// SiYuan - Build Your Eternal Digital Garden
-// Copyright (c) 2020-present, b3log.org
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-//go:build !windows
-
-package util
-
-import "os/exec"
-
-func CmdAttr(cmd *exec.Cmd) {
-}

+ 0 - 26
kernel/util/cmdattr_windows.go

@@ -1,26 +0,0 @@
-// SiYuan - Build Your Eternal Digital Garden
-// Copyright (c) 2020-present, b3log.org
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-package util
-
-import (
-	"os/exec"
-	"syscall"
-)
-
-func CmdAttr(cmd *exec.Cmd) {
-	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
-}

+ 5 - 5
kernel/util/working.go

@@ -417,14 +417,14 @@ func Kill(pid string) {
 	} else {
 		kill = exec.Command("kill", "-9", pid)
 	}
-	CmdAttr(kill)
+	gulu.CmdAttr(kill)
 	kill.CombinedOutput()
 }
 
 func PidByPort(port string) (ret string) {
 	if gulu.OS.IsWindows() {
 		cmd := exec.Command("cmd", "/c", "netstat -ano | findstr "+port)
-		CmdAttr(cmd)
+		gulu.CmdAttr(cmd)
 		data, err := cmd.CombinedOutput()
 		if nil != err {
 			logging.LogErrorf("netstat failed: %s", err)
@@ -444,7 +444,7 @@ func PidByPort(port string) (ret string) {
 	}
 
 	cmd := exec.Command("lsof", "-Fp", "-i", ":"+port)
-	CmdAttr(cmd)
+	gulu.CmdAttr(cmd)
 	data, err := cmd.CombinedOutput()
 	if nil != err {
 		logging.LogErrorf("lsof failed: %s", err)
@@ -507,7 +507,7 @@ func getPandocVer(binPath string) (ret string) {
 	}
 
 	cmd := exec.Command(binPath, "--version")
-	CmdAttr(cmd)
+	gulu.CmdAttr(cmd)
 	data, err := cmd.CombinedOutput()
 	if nil == err && strings.HasPrefix(string(data), "pandoc") {
 		parts := bytes.Split(data, []byte("\n"))
@@ -527,7 +527,7 @@ func IsValidPandocBin(binPath string) bool {
 	}
 
 	cmd := exec.Command(binPath, "--version")
-	CmdAttr(cmd)
+	gulu.CmdAttr(cmd)
 	data, err := cmd.CombinedOutput()
 	if nil == err && strings.HasPrefix(string(data), "pandoc") {
 		return true