2022-05-26 15:18:53 +08:00
|
|
|
|
// 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 model
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"os/exec"
|
2022-07-19 09:43:24 +08:00
|
|
|
|
"path"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
"time"
|
2022-06-03 16:22:39 +08:00
|
|
|
|
"unicode/utf8"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/88250/gulu"
|
|
|
|
|
|
"github.com/dustin/go-humanize"
|
2022-07-05 00:30:47 +08:00
|
|
|
|
"github.com/siyuan-note/dejavu"
|
2022-07-17 12:22:32 +08:00
|
|
|
|
"github.com/siyuan-note/logging"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/sql"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/treenode"
|
|
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
|
syncSameCount = 0
|
|
|
|
|
|
syncDownloadErrCount = 0
|
|
|
|
|
|
fixSyncInterval = 5 * time.Minute
|
2022-07-02 19:36:17 +08:00
|
|
|
|
syncPlanTime = time.Now().Add(fixSyncInterval)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
BootSyncSucc = -1 // -1:未执行,0:执行成功,1:执行失败
|
|
|
|
|
|
ExitSyncSucc = -1
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func AutoSync() {
|
|
|
|
|
|
for {
|
|
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
|
|
if time.Now().After(syncPlanTime) {
|
|
|
|
|
|
SyncData(false, false, false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func SyncData(boot, exit, byHand bool) {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
defer logging.Recover()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2022-06-03 17:11:27 +08:00
|
|
|
|
if !boot && !exit && 2 == Conf.Sync.Mode && !byHand {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if util.IsMutexLocked(&syncLock) {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogWarnf("sync is in progress")
|
2022-07-02 19:36:17 +08:00
|
|
|
|
planSyncAfter(30 * time.Second)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if boot {
|
|
|
|
|
|
util.IncBootProgress(3, "Syncing data from the cloud...")
|
|
|
|
|
|
BootSyncSucc = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
if exit {
|
|
|
|
|
|
ExitSyncSucc = 0
|
|
|
|
|
|
}
|
2022-07-13 17:14:54 +08:00
|
|
|
|
if !IsSubscriber() || !Conf.Sync.Enabled || "" == Conf.Sync.CloudName {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if byHand {
|
|
|
|
|
|
if "" == Conf.Sync.CloudName {
|
|
|
|
|
|
util.PushMsg(Conf.Language(123), 5000)
|
|
|
|
|
|
} else if !Conf.Sync.Enabled {
|
|
|
|
|
|
util.PushMsg(Conf.Language(124), 5000)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !IsValidCloudDirName(Conf.Sync.CloudName) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if boot {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogInfof("sync before boot")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
if exit {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogInfof("sync before exit")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
util.PushMsg(Conf.Language(81), 1000*60*15)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if 7 < syncDownloadErrCount && !byHand {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("sync download error too many times, cancel auto sync, try to sync by hand")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
util.PushErrMsg(Conf.Language(125), 1000*60*60)
|
2022-07-02 19:36:17 +08:00
|
|
|
|
planSyncAfter(64 * time.Minute)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
now := util.CurrentTimeMillis()
|
|
|
|
|
|
Conf.Sync.Synced = now
|
|
|
|
|
|
|
|
|
|
|
|
util.BroadcastByType("main", "syncing", 0, Conf.Language(81), nil)
|
|
|
|
|
|
defer func() {
|
2022-06-06 10:33:42 +08:00
|
|
|
|
synced := util.Millisecond2Time(Conf.Sync.Synced).Format("2006-01-02 15:04:05") + "\n\n" + Conf.Sync.Stat
|
2022-05-26 15:18:53 +08:00
|
|
|
|
msg := fmt.Sprintf(Conf.Language(82), synced)
|
|
|
|
|
|
Conf.Sync.Stat = msg
|
|
|
|
|
|
Conf.Save()
|
|
|
|
|
|
util.BroadcastByType("main", "syncing", 1, msg, nil)
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
2022-07-13 20:03:48 +08:00
|
|
|
|
syncRepo(boot, exit, byHand)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-30 22:40:43 +08:00
|
|
|
|
// incReindex 增量重建索引。
|
|
|
|
|
|
func incReindex(upserts, removes []string) {
|
2022-07-18 09:59:53 +08:00
|
|
|
|
util.IncBootProgress(3, "Sync reindexing...")
|
2022-07-04 22:23:30 +08:00
|
|
|
|
needPushRemoveProgress := 32 < len(removes)
|
2022-07-15 22:48:32 +08:00
|
|
|
|
needPushUpsertProgress := 32 < len(upserts)
|
2022-07-18 00:01:58 +08:00
|
|
|
|
msg := fmt.Sprintf(Conf.Language(35))
|
|
|
|
|
|
util.PushStatusBar(msg)
|
|
|
|
|
|
if needPushRemoveProgress || needPushUpsertProgress {
|
|
|
|
|
|
util.PushEndlessProgress(msg)
|
|
|
|
|
|
}
|
2022-07-15 22:48:32 +08:00
|
|
|
|
|
2022-07-18 10:14:03 +08:00
|
|
|
|
logging.LogDebugf("sync reindex [upserts=%d, removes=%d]", len(upserts), len(removes))
|
2022-07-15 22:48:32 +08:00
|
|
|
|
|
2022-07-18 10:14:03 +08:00
|
|
|
|
// 先执行 remove,否则移动文档时 upsert 会被忽略,导致未被索引
|
2022-07-19 01:05:39 +08:00
|
|
|
|
bootProgressPart := 10 / float64(len(removes))
|
2022-07-15 22:48:32 +08:00
|
|
|
|
for _, removeFile := range removes {
|
|
|
|
|
|
if !strings.HasSuffix(removeFile, ".sy") {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
id := strings.TrimSuffix(filepath.Base(removeFile), ".sy")
|
|
|
|
|
|
block := treenode.GetBlockTree(id)
|
|
|
|
|
|
if nil != block {
|
2022-07-18 00:01:58 +08:00
|
|
|
|
msg = fmt.Sprintf(Conf.Language(39), block.RootID)
|
2022-07-19 09:43:24 +08:00
|
|
|
|
util.IncBootProgress(bootProgressPart, msg)
|
2022-07-15 22:48:32 +08:00
|
|
|
|
util.PushStatusBar(msg)
|
|
|
|
|
|
if needPushRemoveProgress {
|
|
|
|
|
|
util.PushEndlessProgress(msg)
|
|
|
|
|
|
}
|
2022-07-19 09:43:24 +08:00
|
|
|
|
|
|
|
|
|
|
treenode.RemoveBlockTreesByRootID(block.RootID)
|
|
|
|
|
|
sql.RemoveTreeQueue(block.BoxID, block.RootID)
|
2022-07-15 22:48:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-04 22:23:30 +08:00
|
|
|
|
|
2022-07-18 00:01:58 +08:00
|
|
|
|
msg = fmt.Sprintf(Conf.Language(35))
|
|
|
|
|
|
util.PushStatusBar(msg)
|
|
|
|
|
|
if needPushRemoveProgress || needPushUpsertProgress {
|
|
|
|
|
|
util.PushEndlessProgress(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-19 01:05:39 +08:00
|
|
|
|
bootProgressPart = 10 / float64(len(upserts))
|
2022-06-30 22:40:43 +08:00
|
|
|
|
for _, upsertFile := range upserts {
|
|
|
|
|
|
if !strings.HasSuffix(upsertFile, ".sy") {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
upsertFile = filepath.ToSlash(upsertFile)
|
2022-07-01 15:38:05 +08:00
|
|
|
|
if strings.HasPrefix(upsertFile, "/") {
|
|
|
|
|
|
upsertFile = upsertFile[1:]
|
|
|
|
|
|
}
|
2022-07-11 14:26:36 +08:00
|
|
|
|
idx := strings.Index(upsertFile, "/")
|
|
|
|
|
|
if 0 > idx {
|
|
|
|
|
|
// .sy 直接出现在 data 文件夹下,没有出现在笔记本文件夹下的情况
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
box := upsertFile[:idx]
|
2022-06-30 22:40:43 +08:00
|
|
|
|
p := strings.TrimPrefix(upsertFile, box)
|
2022-07-19 09:43:24 +08:00
|
|
|
|
msg = fmt.Sprintf(Conf.Language(40), strings.TrimSuffix(path.Base(p), ".sy"))
|
|
|
|
|
|
util.IncBootProgress(bootProgressPart, msg)
|
|
|
|
|
|
util.PushStatusBar(msg)
|
|
|
|
|
|
if needPushUpsertProgress {
|
|
|
|
|
|
util.PushEndlessProgress(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-30 22:40:43 +08:00
|
|
|
|
tree, err0 := LoadTree(box, p)
|
|
|
|
|
|
if nil != err0 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
treenode.ReindexBlockTree(tree)
|
|
|
|
|
|
sql.UpsertTreeQueue(tree)
|
|
|
|
|
|
}
|
2022-07-04 22:23:30 +08:00
|
|
|
|
|
2022-07-18 00:01:58 +08:00
|
|
|
|
util.PushStatusBar(Conf.Language(58))
|
2022-07-04 22:23:30 +08:00
|
|
|
|
if needPushRemoveProgress || needPushUpsertProgress {
|
2022-07-18 00:01:58 +08:00
|
|
|
|
util.PushEndlessProgress(Conf.Language(58))
|
2022-07-04 22:23:30 +08:00
|
|
|
|
}
|
2022-06-30 22:40:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
func SetCloudSyncDir(name string) {
|
|
|
|
|
|
if Conf.Sync.CloudName == name {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
syncLock.Lock()
|
|
|
|
|
|
defer syncLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
Conf.Sync.CloudName = name
|
|
|
|
|
|
Conf.Save()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func SetSyncEnable(b bool) (err error) {
|
|
|
|
|
|
syncLock.Lock()
|
|
|
|
|
|
defer syncLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
Conf.Sync.Enabled = b
|
|
|
|
|
|
Conf.Save()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-03 17:02:18 +08:00
|
|
|
|
func SetSyncMode(mode int) (err error) {
|
|
|
|
|
|
syncLock.Lock()
|
|
|
|
|
|
defer syncLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
Conf.Sync.Mode = mode
|
|
|
|
|
|
Conf.Save()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
|
var syncLock = sync.Mutex{}
|
|
|
|
|
|
|
|
|
|
|
|
func CreateCloudSyncDir(name string) (err error) {
|
|
|
|
|
|
syncLock.Lock()
|
|
|
|
|
|
defer syncLock.Unlock()
|
|
|
|
|
|
|
2022-06-03 16:20:55 +08:00
|
|
|
|
name = strings.TrimSpace(name)
|
2022-06-11 11:32:00 +08:00
|
|
|
|
name = gulu.Str.RemoveInvisible(name)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if !IsValidCloudDirName(name) {
|
|
|
|
|
|
return errors.New(Conf.Language(37))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-13 17:14:54 +08:00
|
|
|
|
var cloudInfo *dejavu.CloudInfo
|
|
|
|
|
|
cloudInfo, err = buildCloudInfo()
|
|
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
2022-07-13 17:14:54 +08:00
|
|
|
|
|
|
|
|
|
|
err = dejavu.CreateCloudRepo(name, cloudInfo)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RemoveCloudSyncDir(name string) (err error) {
|
|
|
|
|
|
syncLock.Lock()
|
|
|
|
|
|
defer syncLock.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
if "" == name {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-13 17:44:28 +08:00
|
|
|
|
var cloudInfo *dejavu.CloudInfo
|
|
|
|
|
|
cloudInfo, err = buildCloudInfo()
|
|
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
2022-07-05 00:30:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-13 17:44:28 +08:00
|
|
|
|
err = dejavu.RemoveCloudRepo(name, cloudInfo)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if Conf.Sync.CloudName == name {
|
2022-07-06 19:24:24 +08:00
|
|
|
|
Conf.Sync.CloudName = "main"
|
2022-05-26 15:18:53 +08:00
|
|
|
|
Conf.Save()
|
2022-07-06 19:24:24 +08:00
|
|
|
|
util.PushMsg(Conf.Language(155), 5000)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ListCloudSyncDir() (syncDirs []*Sync, hSize string, err error) {
|
|
|
|
|
|
syncDirs = []*Sync{}
|
2022-07-05 00:30:47 +08:00
|
|
|
|
var dirs []map[string]interface{}
|
|
|
|
|
|
var size int64
|
|
|
|
|
|
|
2022-07-13 17:44:28 +08:00
|
|
|
|
var cloudInfo *dejavu.CloudInfo
|
|
|
|
|
|
cloudInfo, err = buildCloudInfo()
|
|
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
2022-07-05 00:30:47 +08:00
|
|
|
|
}
|
2022-07-13 17:44:28 +08:00
|
|
|
|
|
|
|
|
|
|
dirs, size, err = dejavu.GetCloudRepos(cloudInfo)
|
2022-07-05 00:30:47 +08:00
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
for _, d := range dirs {
|
|
|
|
|
|
dirSize := int64(d["size"].(float64))
|
|
|
|
|
|
syncDirs = append(syncDirs, &Sync{
|
|
|
|
|
|
Size: dirSize,
|
|
|
|
|
|
HSize: humanize.Bytes(uint64(dirSize)),
|
|
|
|
|
|
Updated: d["updated"].(string),
|
|
|
|
|
|
CloudName: d["name"].(string),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
hSize = humanize.Bytes(uint64(size))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func formatErrorMsg(err error) string {
|
|
|
|
|
|
msg := err.Error()
|
|
|
|
|
|
if strings.Contains(msg, "Permission denied") || strings.Contains(msg, "Access is denied") {
|
2022-07-13 23:22:19 +08:00
|
|
|
|
msg = Conf.Language(33) + " " + err.Error()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
} else if strings.Contains(msg, "Device or resource busy") {
|
2022-07-13 23:22:19 +08:00
|
|
|
|
msg = Conf.Language(85) + " " + err.Error()
|
2022-07-11 14:26:36 +08:00
|
|
|
|
} else if strings.Contains(msg, "cipher: message authentication failed") {
|
2022-07-13 23:22:19 +08:00
|
|
|
|
msg = Conf.Language(172) + " " + err.Error()
|
2022-07-17 00:47:45 +08:00
|
|
|
|
} else if strings.Contains(msg, "repo fatal error") {
|
|
|
|
|
|
msg = Conf.Language(23) + " " + err.Error()
|
2022-07-17 21:36:41 +08:00
|
|
|
|
} else if strings.Contains(msg, "no such host") || strings.Contains(msg, "connection failed") {
|
|
|
|
|
|
msg = Conf.Language(24)
|
2022-07-19 23:37:10 +08:00
|
|
|
|
} else if strings.Contains(msg, "net/http: request canceled while waiting for connection") {
|
|
|
|
|
|
msg = Conf.Language(24)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
msg = msg + " v" + util.Ver
|
|
|
|
|
|
return msg
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func IsValidCloudDirName(cloudDirName string) bool {
|
2022-07-06 19:13:13 +08:00
|
|
|
|
if 16 < utf8.RuneCountInString(cloudDirName) || 1 > utf8.RuneCountInString(cloudDirName) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chars := []byte{'~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=',
|
|
|
|
|
|
'[', ']', '{', '}', '\\', '|', ';', ':', '\'', '"', '<', ',', '>', '.', '?', '/', ' '}
|
|
|
|
|
|
var charsStr string
|
|
|
|
|
|
for _, char := range chars {
|
|
|
|
|
|
charsStr += string(char)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if strings.ContainsAny(cloudDirName, charsStr) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2022-06-03 16:22:39 +08:00
|
|
|
|
return true
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-27 22:27:06 +08:00
|
|
|
|
func getIgnoreLines() (ret []string) {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
ignore := filepath.Join(util.DataDir, ".siyuan", "syncignore")
|
2022-06-27 22:27:06 +08:00
|
|
|
|
err := os.MkdirAll(filepath.Dir(ignore), 0755)
|
|
|
|
|
|
if nil != err {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
if !gulu.File.IsExist(ignore) {
|
2022-06-27 22:27:06 +08:00
|
|
|
|
if err = gulu.File.WriteFileSafer(ignore, nil, 0644); nil != err {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("create syncignore [%s] failed: %s", ignore, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
data, err := os.ReadFile(ignore)
|
|
|
|
|
|
if nil != err {
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("read syncignore [%s] failed: %s", ignore, err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
dataStr := string(data)
|
|
|
|
|
|
dataStr = strings.ReplaceAll(dataStr, "\r\n", "\n")
|
2022-06-27 22:27:06 +08:00
|
|
|
|
ret = strings.Split(dataStr, "\n")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认忽略帮助文档
|
2022-06-27 22:27:06 +08:00
|
|
|
|
ret = append(ret, "20210808180117-6v0mkxr/**/*")
|
|
|
|
|
|
ret = append(ret, "20210808180117-czj9bvb/**/*")
|
|
|
|
|
|
ret = append(ret, "20211226090932-5lcq56f/**/*")
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
2022-06-27 22:27:06 +08:00
|
|
|
|
ret = gulu.Str.RemoveDuplicatedElem(ret)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-14 21:50:46 +08:00
|
|
|
|
func IncSync() {
|
2022-05-26 15:18:53 +08:00
|
|
|
|
syncSameCount = 0
|
2022-07-02 19:36:17 +08:00
|
|
|
|
planSyncAfter(30 * time.Second)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2022-07-17 12:22:32 +08:00
|
|
|
|
logging.LogErrorf("robocopy data from [%s] to [%s] failed: %s %s", src, dest, string(output), err)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
return gulu.File.Copy(src, dest)
|
|
|
|
|
|
}
|
2022-07-02 19:36:17 +08:00
|
|
|
|
|
|
|
|
|
|
func planSyncAfter(d time.Duration) {
|
|
|
|
|
|
syncPlanTime = time.Now().Add(d)
|
|
|
|
|
|
}
|