2023-06-24 20:39:55 +08:00
// SiYuan - Refactor your thinking
2022-05-26 15:18:53 +08:00
// 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"
2023-06-11 10:13:39 +08:00
"net/http"
2022-05-26 15:18:53 +08:00
"os"
"path/filepath"
2023-06-11 22:53:37 +08:00
"runtime"
2022-05-26 15:18:53 +08:00
"strings"
"sync"
2023-12-08 13:05:50 +08:00
"sync/atomic"
2022-05-26 15:18:53 +08:00
"time"
2024-04-24 19:51:15 +08:00
"github.com/88250/go-humanize"
2022-05-26 15:18:53 +08:00
"github.com/88250/gulu"
2023-05-15 14:56:12 +08:00
"github.com/88250/lute/html"
2023-06-11 10:13:39 +08:00
"github.com/gorilla/websocket"
2023-02-03 20:07:54 +08:00
"github.com/siyuan-note/dejavu"
2022-11-01 00:05:42 +08:00
"github.com/siyuan-note/dejavu/cloud"
2022-07-17 12:22:32 +08:00
"github.com/siyuan-note/logging"
2024-05-11 11:11:15 +08:00
"github.com/siyuan-note/siyuan/kernel/cache"
2022-11-02 20:15:46 +08:00
"github.com/siyuan-note/siyuan/kernel/conf"
2023-02-10 14:28:10 +08:00
"github.com/siyuan-note/siyuan/kernel/filesys"
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"
)
2023-02-08 17:45:47 +08:00
func SyncDataDownload ( ) {
defer logging . Recover ( )
if ! checkSync ( false , false , true ) {
return
}
util . BroadcastByType ( "main" , "syncing" , 0 , Conf . Language ( 81 ) , nil )
2023-04-24 15:29:34 +08:00
if ! isProviderOnline ( true ) { // 这个操作比较耗时,所以要先推送 syncing 事件后再判断网络,这样才能给用户更即时的反馈
2023-02-08 17:45:47 +08:00
util . BroadcastByType ( "main" , "syncing" , 2 , Conf . Language ( 28 ) , nil )
return
}
2023-12-08 13:05:50 +08:00
lockSync ( )
defer unlockSync ( )
2023-02-08 17:45:47 +08:00
now := util . CurrentTimeMillis ( )
Conf . Sync . Synced = now
err := syncRepoDownload ( )
code := 1
2024-09-04 04:40:50 +03:00
if err != nil {
2023-02-08 17:45:47 +08:00
code = 2
}
2023-06-12 11:49:01 +08:00
util . BroadcastByType ( "main" , "syncing" , code , Conf . Sync . Stat , nil )
2023-02-08 17:45:47 +08:00
}
func SyncDataUpload ( ) {
defer logging . Recover ( )
if ! checkSync ( false , false , true ) {
return
}
util . BroadcastByType ( "main" , "syncing" , 0 , Conf . Language ( 81 ) , nil )
2023-04-24 15:29:34 +08:00
if ! isProviderOnline ( true ) { // 这个操作比较耗时,所以要先推送 syncing 事件后再判断网络,这样才能给用户更即时的反馈
2023-02-08 17:45:47 +08:00
util . BroadcastByType ( "main" , "syncing" , 2 , Conf . Language ( 28 ) , nil )
return
}
2023-12-08 13:05:50 +08:00
lockSync ( )
defer unlockSync ( )
2023-02-08 17:45:47 +08:00
now := util . CurrentTimeMillis ( )
Conf . Sync . Synced = now
err := syncRepoUpload ( )
code := 1
2024-09-04 04:40:50 +03:00
if err != nil {
2023-02-08 17:45:47 +08:00
code = 2
}
2023-06-12 11:49:01 +08:00
util . BroadcastByType ( "main" , "syncing" , code , Conf . Sync . Stat , nil )
2023-02-08 17:45:47 +08:00
return
}
2022-05-26 15:18:53 +08:00
var (
2023-12-08 13:05:50 +08:00
syncSameCount = atomic . Int32 { }
2023-04-24 15:29:34 +08:00
autoSyncErrCount = 0
fixSyncInterval = 5 * time . Minute
2023-12-08 13:05:50 +08:00
syncPlanTimeLock = sync . Mutex { }
2023-04-24 15:29:34 +08:00
syncPlanTime = time . Now ( ) . Add ( fixSyncInterval )
2022-05-26 15:18:53 +08:00
BootSyncSucc = - 1 // -1: 未执行, 0: 执行成功, 1: 执行失败
ExitSyncSucc = - 1
)
2023-01-26 23:30:29 +08:00
func SyncDataJob ( ) {
2023-12-08 13:05:50 +08:00
syncPlanTimeLock . Lock ( )
2023-01-27 15:36:43 +08:00
if time . Now ( ) . Before ( syncPlanTime ) {
2023-12-08 13:05:50 +08:00
syncPlanTimeLock . Unlock ( )
2023-01-27 15:36:43 +08:00
return
2022-05-26 15:18:53 +08:00
}
2023-12-08 13:05:50 +08:00
syncPlanTimeLock . Unlock ( )
2023-01-27 15:36:43 +08:00
2023-04-04 10:37:18 +08:00
SyncData ( false )
2022-05-26 15:18:53 +08:00
}
2022-10-22 11:55:12 +08:00
func BootSyncData ( ) {
defer logging . Recover ( )
2023-06-11 23:30:16 +08:00
if Conf . Sync . Perception {
connectSyncWebSocket ( )
}
2023-06-11 10:13:39 +08:00
2023-01-24 15:51:37 +08:00
if ! checkSync ( true , false , false ) {
2022-10-22 11:55:12 +08:00
return
}
2023-04-24 15:29:34 +08:00
if ! isProviderOnline ( false ) {
2023-02-17 10:00:03 +08:00
BootSyncSucc = 1
2023-05-16 09:09:31 +08:00
util . PushErrMsg ( Conf . Language ( 76 ) , 7000 )
2023-02-07 20:35:31 +08:00
return
}
2023-12-08 13:05:50 +08:00
lockSync ( )
defer unlockSync ( )
2022-10-22 11:55:12 +08:00
util . IncBootProgress ( 3 , "Syncing data from the cloud..." )
BootSyncSucc = 0
logging . LogInfof ( "sync before boot" )
now := util . CurrentTimeMillis ( )
Conf . Sync . Synced = now
util . BroadcastByType ( "main" , "syncing" , 0 , Conf . Language ( 81 ) , nil )
err := bootSyncRepo ( )
2023-01-24 15:51:37 +08:00
code := 1
2024-09-04 04:40:50 +03:00
if err != nil {
2023-01-24 15:51:37 +08:00
code = 2
}
2023-06-12 11:49:01 +08:00
util . BroadcastByType ( "main" , "syncing" , code , Conf . Sync . Stat , nil )
2022-10-22 11:55:12 +08:00
return
}
2023-04-04 10:37:18 +08:00
func SyncData ( byHand bool ) {
2023-11-25 17:13:43 +08:00
syncData ( false , byHand )
2023-01-18 23:00:30 +08:00
}
2023-12-08 13:05:50 +08:00
func lockSync ( ) {
syncLock . Lock ( )
2023-12-08 17:09:56 +08:00
isSyncing . Store ( true )
2023-12-08 13:05:50 +08:00
}
func unlockSync ( ) {
2023-12-08 17:09:56 +08:00
isSyncing . Store ( false )
2023-12-08 13:05:50 +08:00
syncLock . Unlock ( )
}
2023-11-25 17:13:43 +08:00
func syncData ( exit , byHand bool ) {
2022-07-17 12:22:32 +08:00
defer logging . Recover ( )
2022-05-26 15:18:53 +08:00
2023-04-04 10:37:18 +08:00
if ! checkSync ( false , exit , byHand ) {
2023-02-07 20:35:31 +08:00
return
}
2023-12-08 17:09:56 +08:00
lockSync ( )
defer unlockSync ( )
2023-02-07 20:35:31 +08:00
util . BroadcastByType ( "main" , "syncing" , 0 , Conf . Language ( 81 ) , nil )
2023-04-24 15:29:34 +08:00
if ! exit && ! isProviderOnline ( byHand ) { // 这个操作比较耗时,所以要先推送 syncing 事件后再判断网络,这样才能给用户更即时的反馈
2023-02-07 20:35:31 +08:00
util . BroadcastByType ( "main" , "syncing" , 2 , Conf . Language ( 28 ) , nil )
2022-05-26 15:18:53 +08:00
return
}
if exit {
ExitSyncSucc = 0
2023-01-20 10:01:10 +08:00
logging . LogInfof ( "sync before exit" )
2024-02-06 21:30:36 +08:00
msgId := util . PushMsg ( Conf . Language ( 81 ) , 1000 * 60 * 15 )
defer func ( ) {
util . PushClearMsg ( msgId )
} ( )
2023-01-20 10:01:10 +08:00
}
now := util . CurrentTimeMillis ( )
Conf . Sync . Synced = now
2023-06-12 21:38:15 +08:00
dataChanged , err := syncRepo ( exit , byHand )
2023-01-24 11:08:56 +08:00
code := 1
2024-09-04 04:40:50 +03:00
if err != nil {
2023-01-24 11:08:56 +08:00
code = 2
}
2023-06-12 11:49:01 +08:00
util . BroadcastByType ( "main" , "syncing" , code , Conf . Sync . Stat , nil )
2023-06-11 22:53:37 +08:00
2023-06-11 23:30:16 +08:00
if nil == webSocketConn && Conf . Sync . Perception {
2023-06-11 22:53:37 +08:00
// 如果 websocket 连接已经断开,则重新连接
connectSyncWebSocket ( )
}
2023-11-25 17:13:43 +08:00
if 1 == Conf . Sync . Mode && nil != webSocketConn && Conf . Sync . Perception && dataChanged {
2025-01-09 21:59:54 +08:00
// 如果处于自动同步模式且不是由 WS 触发的同步,则通知其他设备上的内核进行同步
2023-06-11 22:53:37 +08:00
request := map [ string ] interface { } {
"cmd" : "synced" ,
"synced" : Conf . Sync . Synced ,
}
if writeErr := webSocketConn . WriteJSON ( request ) ; nil != writeErr {
logging . LogErrorf ( "write websocket message failed: %v" , writeErr )
}
}
2023-01-20 10:01:10 +08:00
return
}
func checkSync ( boot , exit , byHand bool ) bool {
2023-02-08 17:45:47 +08:00
if 2 == Conf . Sync . Mode && ! boot && ! exit && ! byHand { // 手动模式下只有启动和退出进行同步
return false
}
if 3 == Conf . Sync . Mode && ! byHand { // 完全手动模式下只有手动进行同步
2023-01-20 10:01:10 +08:00
return false
2022-05-26 15:18:53 +08:00
}
2022-11-02 20:15:46 +08:00
if ! Conf . Sync . Enabled {
2022-05-26 15:18:53 +08:00
if byHand {
2022-11-02 20:15:46 +08:00
util . PushMsg ( Conf . Language ( 124 ) , 5000 )
2022-05-26 15:18:53 +08:00
}
2023-01-20 10:01:10 +08:00
return false
2022-05-26 15:18:53 +08:00
}
2022-11-02 20:15:46 +08:00
if ! cloud . IsValidCloudDirName ( Conf . Sync . CloudName ) {
if byHand {
util . PushMsg ( Conf . Language ( 123 ) , 5000 )
}
2023-01-20 10:01:10 +08:00
return false
2022-11-02 20:15:46 +08:00
}
2023-07-19 12:15:37 +08:00
switch Conf . Sync . Provider {
case conf . ProviderSiYuan :
if ! IsSubscriber ( ) {
return false
}
2024-12-31 21:06:13 +08:00
case conf . ProviderWebDAV , conf . ProviderS3 , conf . ProviderLocal :
2023-09-25 16:38:24 +08:00
if ! IsPaidUser ( ) {
2023-07-19 12:15:37 +08:00
return false
}
2022-11-02 20:15:46 +08:00
}
2023-04-24 15:29:34 +08:00
if 7 < autoSyncErrCount && ! byHand {
logging . LogErrorf ( "failed to auto-sync too many times, delay auto-sync 64 minutes" )
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 )
2023-01-20 10:01:10 +08:00
return false
2023-01-24 15:51:37 +08:00
}
2023-01-20 10:01:10 +08:00
return true
2022-05-26 15:18:53 +08:00
}
2022-06-30 22:40:43 +08:00
// incReindex 增量重建索引。
2023-04-25 18:31:22 +08:00
func incReindex ( upserts , removes [ ] string ) ( upsertRootIDs , removeRootIDs [ ] string ) {
2023-04-25 19:39:46 +08:00
upsertRootIDs = [ ] string { }
removeRootIDs = [ ] string { }
2022-07-18 09:59:53 +08:00
util . IncBootProgress ( 3 , "Sync reindexing..." )
2024-01-05 20:54:46 +08:00
removeRootIDs = removeIndexes ( removes ) // 先执行 remove, 否则移动文档时 upsert 会被忽略,导致未被索引
upsertRootIDs = upsertIndexes ( upserts )
2024-01-16 21:42:34 +08:00
if 1 > len ( removeRootIDs ) {
removeRootIDs = [ ] string { }
}
if 1 > len ( upsertRootIDs ) {
upsertRootIDs = [ ] string { }
}
2024-01-05 20:54:46 +08:00
return
}
2022-07-15 22:48:32 +08:00
2024-01-05 20:54:46 +08:00
func removeIndexes ( removeFilePaths [ ] string ) ( removeRootIDs [ ] string ) {
bootProgressPart := int32 ( 10 / float64 ( len ( removeFilePaths ) ) )
for _ , removeFile := range removeFilePaths {
2022-07-15 22:48:32 +08:00
if ! strings . HasSuffix ( removeFile , ".sy" ) {
continue
}
2024-11-29 08:41:43 +08:00
id := util . GetTreeID ( removeFile )
2023-04-25 18:31:22 +08:00
removeRootIDs = append ( removeRootIDs , id )
2022-07-15 22:48:32 +08:00
block := treenode . GetBlockTree ( id )
if nil != block {
2024-01-05 20:54:46 +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 )
2022-07-19 09:43:24 +08:00
2024-05-11 11:11:15 +08:00
bts := treenode . GetBlockTreesByRootID ( block . RootID )
for _ , b := range bts {
cache . RemoveBlockIAL ( b . ID )
}
cache . RemoveDocIAL ( block . Path )
2022-07-19 09:43:24 +08:00
treenode . RemoveBlockTreesByRootID ( block . RootID )
2024-03-15 22:53:37 +08:00
sql . RemoveTreeQueue ( block . RootID )
2022-07-15 22:48:32 +08:00
}
}
2024-01-16 21:42:34 +08:00
if 1 > len ( removeRootIDs ) {
removeRootIDs = [ ] string { }
}
2024-01-05 20:54:46 +08:00
return
}
2022-07-04 22:23:30 +08:00
2024-01-05 20:54:46 +08:00
func upsertIndexes ( upsertFilePaths [ ] string ) ( upsertRootIDs [ ] string ) {
luteEngine := util . NewLute ( )
bootProgressPart := int32 ( 10 / float64 ( len ( upsertFilePaths ) ) )
for _ , upsertFile := range upsertFilePaths {
2022-06-30 22:40:43 +08:00
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 )
2024-11-29 08:41:43 +08:00
msg := fmt . Sprintf ( Conf . Language ( 40 ) , util . GetTreeID ( p ) )
2022-07-19 09:43:24 +08:00
util . IncBootProgress ( bootProgressPart , msg )
util . PushStatusBar ( msg )
2023-02-10 14:28:10 +08:00
tree , err0 := filesys . LoadTree ( box , p , luteEngine )
2022-06-30 22:40:43 +08:00
if nil != err0 {
continue
}
2024-06-20 22:53:27 +08:00
treenode . UpsertBlockTree ( tree )
2022-06-30 22:40:43 +08:00
sql . UpsertTreeQueue ( tree )
2024-05-11 11:11:15 +08:00
bts := treenode . GetBlockTreesByRootID ( tree . ID )
for _ , b := range bts {
cache . RemoveBlockIAL ( b . ID )
}
cache . RemoveDocIAL ( tree . Path )
2023-04-25 18:31:22 +08:00
upsertRootIDs = append ( upsertRootIDs , tree . Root . ID )
2022-06-30 22:40:43 +08:00
}
2024-01-16 21:42:34 +08:00
if 1 > len ( upsertRootIDs ) {
upsertRootIDs = [ ] string { }
}
2023-04-25 18:31:22 +08:00
return
2022-06-30 22:40:43 +08:00
}
2022-05-26 15:18:53 +08:00
func SetCloudSyncDir ( name string ) {
2023-09-07 11:55:43 +08:00
if ! cloud . IsValidCloudDirName ( name ) {
util . PushErrMsg ( Conf . Language ( 37 ) , 5000 )
return
}
2022-05-26 15:18:53 +08:00
if Conf . Sync . CloudName == name {
return
}
Conf . Sync . CloudName = name
Conf . Save ( )
}
2022-10-09 10:36:41 +08:00
func SetSyncGenerateConflictDoc ( b bool ) {
Conf . Sync . GenerateConflictDoc = b
Conf . Save ( )
return
}
2023-01-24 13:21:23 +08:00
func SetSyncEnable ( b bool ) {
2022-05-26 15:18:53 +08:00
Conf . Sync . Enabled = b
Conf . Save ( )
2023-06-11 23:24:17 +08:00
return
}
2024-12-14 12:02:14 +08:00
func SetSyncInterval ( interval int ) {
if 30 > interval {
interval = 30
}
if 43200 < interval {
interval = 43200
}
Conf . Sync . Interval = interval
Conf . Save ( )
planSyncAfter ( time . Duration ( interval ) * time . Second )
return
}
2023-06-11 23:24:17 +08:00
func SetSyncPerception ( b bool ) {
2023-06-12 08:25:16 +08:00
if util . ContainerDocker == util . Container {
b = false
}
2023-06-11 23:24:17 +08:00
Conf . Sync . Perception = b
Conf . Save ( )
2023-06-11 23:29:09 +08:00
if b {
connectSyncWebSocket ( )
} else {
closeSyncWebSocket ( )
}
2022-05-26 15:18:53 +08:00
return
}
2023-06-12 22:08:42 +08:00
func SetSyncMode ( mode int ) {
2022-06-03 17:02:18 +08:00
Conf . Sync . Mode = mode
Conf . Save ( )
return
}
2022-11-11 15:33:05 +08:00
func SetSyncProvider ( provider int ) ( err error ) {
Conf . Sync . Provider = provider
Conf . Save ( )
return
}
func SetSyncProviderS3 ( s3 * conf . S3 ) ( err error ) {
2022-11-24 22:08:58 +08:00
s3 . Endpoint = strings . TrimSpace ( s3 . Endpoint )
2022-11-25 17:07:07 +08:00
s3 . Endpoint = util . NormalizeEndpoint ( s3 . Endpoint )
2022-11-24 22:08:58 +08:00
s3 . AccessKey = strings . TrimSpace ( s3 . AccessKey )
s3 . SecretKey = strings . TrimSpace ( s3 . SecretKey )
s3 . Bucket = strings . TrimSpace ( s3 . Bucket )
s3 . Region = strings . TrimSpace ( s3 . Region )
2022-12-04 20:42:35 +08:00
s3 . Timeout = util . NormalizeTimeout ( s3 . Timeout )
2024-10-16 19:29:11 +08:00
s3 . ConcurrentReqs = util . NormalizeConcurrentReqs ( s3 . ConcurrentReqs , conf . ProviderS3 )
2022-11-24 22:08:58 +08:00
2023-09-07 11:55:43 +08:00
if ! cloud . IsValidCloudDirName ( s3 . Bucket ) {
util . PushErrMsg ( Conf . Language ( 37 ) , 5000 )
return
}
2022-11-11 15:33:05 +08:00
Conf . Sync . S3 = s3
Conf . Save ( )
return
}
func SetSyncProviderWebDAV ( webdav * conf . WebDAV ) ( err error ) {
2022-11-24 22:08:58 +08:00
webdav . Endpoint = strings . TrimSpace ( webdav . Endpoint )
2022-11-25 17:07:07 +08:00
webdav . Endpoint = util . NormalizeEndpoint ( webdav . Endpoint )
2023-03-14 11:48:28 +08:00
// 不支持配置坚果云 WebDAV 进行同步 https://github.com/siyuan-note/siyuan/issues/7657
if strings . Contains ( strings . ToLower ( webdav . Endpoint ) , "dav.jianguoyun.com" ) {
err = errors . New ( Conf . Language ( 194 ) )
return
}
2022-11-24 22:08:58 +08:00
webdav . Username = strings . TrimSpace ( webdav . Username )
webdav . Password = strings . TrimSpace ( webdav . Password )
2022-12-04 20:42:35 +08:00
webdav . Timeout = util . NormalizeTimeout ( webdav . Timeout )
2024-10-16 19:29:11 +08:00
webdav . ConcurrentReqs = util . NormalizeConcurrentReqs ( webdav . ConcurrentReqs , conf . ProviderWebDAV )
2022-11-24 22:08:58 +08:00
2022-11-11 15:33:05 +08:00
Conf . Sync . WebDAV = webdav
Conf . Save ( )
return
}
2024-12-31 21:06:13 +08:00
func SetSyncProviderLocal ( local * conf . Local ) ( err error ) {
local . Endpoint = strings . TrimSpace ( local . Endpoint )
local . Endpoint = util . NormalizeLocalPath ( local . Endpoint )
2024-12-31 23:24:52 +08:00
absPath , err := filepath . Abs ( local . Endpoint )
if nil != err {
msg := fmt . Sprintf ( "get endpoint [%s] abs path failed: %s" , local . Endpoint , err )
logging . LogErrorf ( msg )
err = errors . New ( fmt . Sprintf ( Conf . Language ( 77 ) , msg ) )
return
}
if ! gulu . File . IsExist ( absPath ) {
2025-01-02 20:28:43 +08:00
msg := fmt . Sprintf ( "endpoint [%s] not exist" , local . Endpoint )
2024-12-31 23:24:52 +08:00
logging . LogErrorf ( msg )
err = errors . New ( fmt . Sprintf ( Conf . Language ( 77 ) , msg ) )
return
}
2025-01-02 20:28:43 +08:00
if util . IsAbsPathInWorkspace ( absPath ) || filepath . Clean ( absPath ) == filepath . Clean ( util . WorkspaceDir ) {
msg := fmt . Sprintf ( "endpoint [%s] is in workspace" , local . Endpoint )
2024-12-31 23:24:52 +08:00
logging . LogErrorf ( msg )
err = errors . New ( fmt . Sprintf ( Conf . Language ( 77 ) , msg ) )
return
}
2025-01-10 23:00:40 +08:00
if util . IsSubPath ( absPath , util . WorkspaceDir ) {
msg := fmt . Sprintf ( "endpoint [%s] is parent of workspace" , local . Endpoint )
logging . LogErrorf ( msg )
err = errors . New ( fmt . Sprintf ( Conf . Language ( 77 ) , msg ) )
return
}
2024-12-31 21:06:13 +08:00
local . Timeout = util . NormalizeTimeout ( local . Timeout )
local . ConcurrentReqs = util . NormalizeConcurrentReqs ( local . ConcurrentReqs , conf . ProviderLocal )
Conf . Sync . Local = local
Conf . Save ( )
return
}
2023-12-08 13:05:50 +08:00
var (
syncLock = sync . Mutex { }
2023-12-08 17:09:56 +08:00
isSyncing = atomic . Bool { }
2023-12-08 13:05:50 +08:00
)
2022-05-26 15:18:53 +08:00
func CreateCloudSyncDir ( name string ) ( err error ) {
2024-12-31 21:06:13 +08:00
switch Conf . Sync . Provider {
case conf . ProviderSiYuan , conf . ProviderLocal :
break
default :
2022-11-11 19:45:41 +08:00
err = errors . New ( Conf . Language ( 131 ) )
return
}
2022-06-03 16:20:55 +08:00
name = strings . TrimSpace ( name )
2024-11-27 20:13:22 +08:00
name = util . RemoveInvalid ( name )
2022-11-02 20:10:59 +08:00
if ! cloud . IsValidCloudDirName ( name ) {
2022-05-26 15:18:53 +08:00
return errors . New ( Conf . Language ( 37 ) )
}
2022-10-31 20:59:00 +08:00
repo , err := newRepository ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2022-07-13 17:14:54 +08:00
return
2022-05-26 15:18:53 +08:00
}
2022-07-13 17:14:54 +08:00
2022-10-31 20:59:00 +08:00
err = repo . CreateCloudRepo ( name )
2024-09-04 04:40:50 +03:00
if err != nil {
2023-04-06 15:00:03 +08:00
err = errors . New ( formatRepoErrorMsg ( err ) )
2022-11-11 19:45:41 +08:00
return
}
2022-05-26 15:18:53 +08:00
return
}
func RemoveCloudSyncDir ( name string ) ( err error ) {
2024-12-31 21:06:13 +08:00
switch Conf . Sync . Provider {
case conf . ProviderSiYuan , conf . ProviderLocal :
break
default :
2022-11-11 19:45:41 +08:00
err = errors . New ( Conf . Language ( 131 ) )
return
}
2022-07-21 14:56:07 +08:00
msgId := util . PushMsg ( Conf . Language ( 116 ) , 15000 )
2022-05-26 15:18:53 +08:00
if "" == name {
return
}
2022-10-31 20:59:00 +08:00
repo , err := newRepository ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2022-07-13 17:44:28 +08:00
return
2022-07-05 00:30:47 +08:00
}
2022-10-31 20:59:00 +08:00
err = repo . RemoveCloudRepo ( name )
2024-09-04 04:40:50 +03:00
if err != nil {
2023-04-06 15:00:03 +08:00
err = errors . New ( formatRepoErrorMsg ( err ) )
2022-05-26 15:18:53 +08:00
return
}
2022-07-21 14:56:07 +08:00
util . PushClearMsg ( msgId )
2022-07-21 15:01:43 +08:00
time . Sleep ( 500 * time . Millisecond )
2022-05-26 15:18:53 +08:00
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-11-02 11:11:03 +08:00
var dirs [ ] * cloud . Repo
2022-07-05 00:30:47 +08:00
var size int64
2022-10-31 20:59:00 +08:00
repo , err := newRepository ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2022-07-13 17:44:28 +08:00
return
2022-07-05 00:30:47 +08:00
}
2022-07-13 17:44:28 +08:00
2022-10-31 20:59:00 +08:00
dirs , size , err = repo . GetCloudRepos ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2023-04-06 15:00:03 +08:00
err = errors . New ( formatRepoErrorMsg ( err ) )
2022-07-05 00:30:47 +08:00
return
}
2022-08-21 23:20:22 +08:00
if 1 > len ( dirs ) {
2022-11-02 11:11:03 +08:00
dirs = append ( dirs , & cloud . Repo {
Name : "main" ,
Size : 0 ,
Updated : time . Now ( ) . Format ( "2006-01-02 15:04:05" ) ,
2022-08-21 23:20:22 +08:00
} )
}
2022-05-26 15:18:53 +08:00
for _ , d := range dirs {
2022-11-02 11:11:03 +08:00
dirSize := d . Size
2022-11-09 19:52:30 +08:00
sync := & Sync {
2022-05-26 15:18:53 +08:00
Size : dirSize ,
2022-11-09 19:52:30 +08:00
HSize : "-" ,
2022-11-02 11:11:03 +08:00
Updated : d . Updated ,
CloudName : d . Name ,
2022-11-09 19:52:30 +08:00
}
if conf . ProviderSiYuan == Conf . Sync . Provider {
2024-04-24 20:05:42 +08:00
sync . HSize = humanize . BytesCustomCeil ( uint64 ( dirSize ) , 2 )
2022-11-09 19:52:30 +08:00
}
syncDirs = append ( syncDirs , sync )
}
hSize = "-"
if conf . ProviderSiYuan == Conf . Sync . Provider {
2024-04-24 20:05:42 +08:00
hSize = humanize . BytesCustomCeil ( uint64 ( size ) , 2 )
2022-05-26 15:18:53 +08:00
}
2025-01-01 12:00:02 +08:00
if conf . ProviderS3 == Conf . Sync . Provider {
Conf . Sync . CloudName = syncDirs [ 0 ] . CloudName
Conf . Save ( )
}
2022-05-26 15:18:53 +08:00
return
}
2023-04-06 15:00:03 +08:00
func formatRepoErrorMsg ( err error ) string {
2023-05-15 14:56:12 +08:00
msg := html . EscapeString ( err . Error ( ) )
2023-02-03 20:07:54 +08:00
if errors . Is ( err , cloud . ErrCloudAuthFailed ) {
msg = Conf . Language ( 31 )
} else if errors . Is ( err , cloud . ErrCloudObjectNotFound ) {
2022-07-30 10:39:14 +08:00
msg = Conf . Language ( 129 )
2023-02-03 20:07:54 +08:00
} else if errors . Is ( err , dejavu . ErrLockCloudFailed ) {
2023-02-03 17:18:38 +08:00
msg = Conf . Language ( 188 )
2023-02-03 20:07:54 +08:00
} else if errors . Is ( err , dejavu . ErrCloudLocked ) {
2023-02-03 17:18:38 +08:00
msg = Conf . Language ( 189 )
2023-12-21 23:07:51 +08:00
} else if errors . Is ( err , dejavu . ErrRepoFatal ) {
2023-02-03 20:07:54 +08:00
msg = Conf . Language ( 23 )
2023-03-15 11:46:30 +08:00
} else if errors . Is ( err , cloud . ErrSystemTimeIncorrect ) {
msg = Conf . Language ( 195 )
2023-07-04 19:16:35 +08:00
} else if errors . Is ( err , cloud . ErrDeprecatedVersion ) {
msg = Conf . Language ( 212 )
} else if errors . Is ( err , cloud . ErrCloudCheckFailed ) {
msg = Conf . Language ( 213 )
2023-11-10 23:00:17 +08:00
} else if errors . Is ( err , cloud . ErrCloudServiceUnavailable ) {
msg = Conf . language ( 219 )
2024-10-26 22:41:58 +08:00
} else if errors . Is ( err , cloud . ErrCloudForbidden ) {
msg = Conf . language ( 249 )
} else if errors . Is ( err , cloud . ErrCloudTooManyRequests ) {
msg = Conf . language ( 250 )
2023-02-03 20:07:54 +08:00
} else {
2024-08-01 20:25:53 +08:00
logging . LogErrorf ( "sync failed caused by network: %s" , msg )
2023-02-03 20:07:54 +08:00
msgLowerCase := strings . ToLower ( msg )
if strings . Contains ( msgLowerCase , "permission denied" ) || strings . Contains ( msg , "access is denied" ) {
msg = Conf . Language ( 33 )
2025-01-09 11:16:08 +08:00
} else if strings . Contains ( msgLowerCase , "region was not a valid" ) {
2025-01-02 09:39:02 +08:00
msg = Conf . language ( 254 )
2023-02-03 20:07:54 +08:00
} else if strings . Contains ( msgLowerCase , "device or resource busy" ) || strings . Contains ( msg , "is being used by another" ) {
msg = fmt . Sprintf ( Conf . Language ( 85 ) , err )
} else if strings . Contains ( msgLowerCase , "cipher: message authentication failed" ) {
msg = Conf . Language ( 135 )
} else if strings . Contains ( msgLowerCase , "no such host" ) || strings . Contains ( msgLowerCase , "connection failed" ) || strings . Contains ( msgLowerCase , "hostname resolution" ) || strings . Contains ( msgLowerCase , "No address associated with hostname" ) {
msg = Conf . Language ( 24 )
} else if strings . Contains ( msgLowerCase , "net/http: request canceled while waiting for connection" ) || strings . Contains ( msgLowerCase , "exceeded while awaiting" ) || strings . Contains ( msgLowerCase , "context deadline exceeded" ) || strings . Contains ( msgLowerCase , "timeout" ) || strings . Contains ( msgLowerCase , "context cancellation while reading body" ) {
msg = Conf . Language ( 24 )
2024-12-20 23:09:18 +08:00
} else if strings . Contains ( msgLowerCase , "connection" ) || strings . Contains ( msgLowerCase , "refused" ) || strings . Contains ( msgLowerCase , "socket" ) || strings . Contains ( msgLowerCase , "eof" ) || strings . Contains ( msgLowerCase , "closed" ) || strings . Contains ( msgLowerCase , "network" ) {
2023-02-03 20:07:54 +08:00
msg = Conf . Language ( 28 )
}
2022-05-26 15:18:53 +08:00
}
2023-04-06 15:00:03 +08:00
msg += " (Provider: " + conf . ProviderToStr ( Conf . Sync . Provider ) + ")"
2022-05-26 15:18:53 +08:00
return msg
}
2024-01-06 20:14:39 +08:00
func getSyncIgnoreLines ( ) ( 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 )
2024-09-04 04:40:50 +03:00
if err != nil {
2022-06-27 22:27:06 +08:00
return
}
2022-05-26 15:18:53 +08:00
if ! gulu . File . IsExist ( ignore ) {
2024-09-04 04:40:50 +03:00
if err = gulu . File . WriteFileSafer ( ignore , nil , 0644 ) ; err != nil {
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 )
2024-09-04 04:40:50 +03:00
if err != nil {
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/**/*" )
2024-05-31 11:05:57 +09:00
ret = append ( ret , "20240530133126-axarxgx/**/*" )
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 ( ) {
2023-12-08 13:05:50 +08:00
syncSameCount . Store ( 0 )
2024-12-14 12:02:14 +08:00
planSyncAfter ( time . Duration ( Conf . Sync . Interval ) * time . Second )
2022-05-26 15:18:53 +08:00
}
2022-07-02 19:36:17 +08:00
func planSyncAfter ( d time . Duration ) {
2023-12-08 13:05:50 +08:00
syncPlanTimeLock . Lock ( )
2022-07-02 19:36:17 +08:00
syncPlanTime = time . Now ( ) . Add ( d )
2023-12-08 13:05:50 +08:00
syncPlanTimeLock . Unlock ( )
2022-07-02 19:36:17 +08:00
}
2023-03-29 14:43:02 +08:00
2023-04-24 15:29:34 +08:00
func isProviderOnline ( byHand bool ) ( ret bool ) {
2025-04-27 12:06:57 +08:00
var checkURL string
2023-04-05 19:54:13 +08:00
skipTlsVerify := false
2024-10-08 17:06:13 +08:00
timeout := 3000
2023-03-29 14:43:02 +08:00
switch Conf . Sync . Provider {
case conf . ProviderSiYuan :
2025-04-27 12:06:57 +08:00
checkURL = util . GetCloudSyncServer ( )
2023-03-29 14:43:02 +08:00
case conf . ProviderS3 :
2023-03-29 15:07:13 +08:00
checkURL = Conf . Sync . S3 . Endpoint
2023-04-05 19:54:13 +08:00
skipTlsVerify = Conf . Sync . S3 . SkipTlsVerify
2024-10-08 17:06:13 +08:00
timeout = Conf . Sync . S3 . Timeout * 1000
2023-03-29 14:43:02 +08:00
case conf . ProviderWebDAV :
2023-03-29 15:07:13 +08:00
checkURL = Conf . Sync . WebDAV . Endpoint
2023-04-05 19:54:13 +08:00
skipTlsVerify = Conf . Sync . WebDAV . SkipTlsVerify
2024-10-08 17:06:13 +08:00
timeout = Conf . Sync . WebDAV . Timeout * 1000
2024-12-31 21:06:13 +08:00
case conf . ProviderLocal :
checkURL = "file://" + Conf . Sync . Local . Endpoint
timeout = Conf . Sync . Local . Timeout * 1000
2023-03-29 14:43:02 +08:00
default :
2023-03-29 15:07:13 +08:00
logging . LogWarnf ( "unknown provider: %d" , Conf . Sync . Provider )
2023-04-04 10:21:34 +08:00
return false
2023-03-29 14:43:02 +08:00
}
2023-03-29 15:04:58 +08:00
2024-10-08 17:06:13 +08:00
if ret = util . IsOnline ( checkURL , skipTlsVerify , timeout ) ; ! ret {
2023-04-24 15:29:34 +08:00
if 1 > autoSyncErrCount || byHand {
util . PushErrMsg ( Conf . Language ( 76 ) + " (Provider: " + conf . ProviderToStr ( Conf . Sync . Provider ) + ")" , 5000 )
}
2023-04-24 15:44:43 +08:00
if ! byHand {
2023-04-24 15:50:02 +08:00
planSyncAfter ( fixSyncInterval )
2023-04-24 15:44:43 +08:00
autoSyncErrCount ++
}
2023-03-29 15:04:58 +08:00
}
return
2023-03-29 14:43:02 +08:00
}
2023-06-11 10:13:39 +08:00
2023-06-11 22:53:37 +08:00
var (
webSocketConn * websocket . Conn
webSocketConnLock = sync . Mutex { }
)
type OnlineKernel struct {
ID string ` json:"id" `
Hostname string ` json:"hostname" `
OS string ` json:"os" `
Ver string ` json:"ver" `
}
var (
onlineKernels [ ] * OnlineKernel
onlineKernelsLock = sync . Mutex { }
)
2023-06-12 10:45:47 +08:00
func GetOnlineKernels ( ) ( ret [ ] * OnlineKernel ) {
ret = [ ] * OnlineKernel { }
2023-06-12 10:37:54 +08:00
onlineKernelsLock . Lock ( )
2023-06-12 10:45:47 +08:00
tmp := onlineKernels
onlineKernelsLock . Unlock ( )
for _ , kernel := range tmp {
2023-06-12 12:04:28 +08:00
if kernel . ID == KernelID {
2023-06-12 10:45:47 +08:00
continue
}
ret = append ( ret , kernel )
}
return
2023-06-12 10:37:54 +08:00
}
2023-12-08 13:15:52 +08:00
var closedSyncWebSocket = atomic . Bool { }
2023-06-11 23:41:26 +08:00
2023-06-11 23:29:09 +08:00
func closeSyncWebSocket ( ) {
2023-06-11 23:31:33 +08:00
defer logging . Recover ( )
2023-06-11 23:29:09 +08:00
webSocketConnLock . Lock ( )
defer webSocketConnLock . Unlock ( )
if nil != webSocketConn {
webSocketConn . Close ( )
webSocketConn = nil
2023-12-08 13:15:52 +08:00
closedSyncWebSocket . Store ( true )
2023-06-11 23:29:09 +08:00
}
2023-06-11 23:31:33 +08:00
logging . LogInfof ( "sync websocket closed" )
2023-06-11 23:29:09 +08:00
}
2023-06-11 10:13:39 +08:00
func connectSyncWebSocket ( ) {
defer logging . Recover ( )
if ! Conf . Sync . Enabled || ! IsSubscriber ( ) || conf . ProviderSiYuan != Conf . Sync . Provider {
return
}
2023-06-11 23:02:45 +08:00
if util . ContainerDocker == util . Container {
return
}
2023-06-11 22:53:37 +08:00
webSocketConnLock . Lock ( )
defer webSocketConnLock . Unlock ( )
if nil != webSocketConn {
return
}
2023-06-12 08:00:39 +08:00
//logging.LogInfof("connecting sync websocket...")
2023-06-11 22:53:37 +08:00
var dialErr error
webSocketConn , dialErr = dialSyncWebSocket ( )
2023-06-11 10:13:39 +08:00
if nil != dialErr {
logging . LogWarnf ( "connect sync websocket failed: %s" , dialErr )
return
}
logging . LogInfof ( "sync websocket connected" )
2023-06-11 23:41:26 +08:00
2023-06-11 22:53:37 +08:00
webSocketConn . SetCloseHandler ( func ( code int , text string ) error {
2023-06-11 10:13:39 +08:00
logging . LogWarnf ( "sync websocket closed: %d, %s" , code , text )
return nil
} )
go func ( ) {
defer logging . Recover ( )
for {
2023-06-11 22:53:37 +08:00
result := gulu . Ret . NewResult ( )
if readErr := webSocketConn . ReadJSON ( & result ) ; nil != readErr {
2023-06-11 23:41:26 +08:00
time . Sleep ( 1 * time . Second )
2023-12-08 13:15:52 +08:00
if closedSyncWebSocket . Load ( ) {
2023-06-11 23:41:26 +08:00
return
}
2023-06-11 10:13:39 +08:00
reconnected := false
for retries := 0 ; retries < 7 ; retries ++ {
time . Sleep ( 7 * time . Second )
2023-12-08 21:46:46 +08:00
if nil == Conf . GetUser ( ) {
2023-06-21 15:31:28 +08:00
return
}
2023-06-12 08:25:16 +08:00
//logging.LogInfof("reconnecting sync websocket...")
2023-06-11 22:53:37 +08:00
webSocketConn , dialErr = dialSyncWebSocket ( )
2023-06-11 10:13:39 +08:00
if nil != dialErr {
logging . LogWarnf ( "reconnect sync websocket failed: %s" , dialErr )
continue
}
2023-06-12 08:25:16 +08:00
logging . LogInfof ( "sync websocket reconnected" )
reconnected = true
break
2023-06-11 10:13:39 +08:00
}
if ! reconnected {
logging . LogWarnf ( "reconnect sync websocket failed, do not retry" )
2023-06-11 22:53:37 +08:00
webSocketConn = nil
2023-06-11 10:13:39 +08:00
return
}
continue
}
logging . LogInfof ( "sync websocket message: %v" , result )
2023-06-11 22:53:37 +08:00
data := result . Data . ( map [ string ] interface { } )
switch data [ "cmd" ] . ( string ) {
case "synced" :
2024-11-02 12:09:35 +08:00
// Improve data synchronization perception https://github.com/siyuan-note/siyuan/issues/13000
SyncDataDownload ( )
2023-06-11 22:53:37 +08:00
case "kernels" :
onlineKernelsLock . Lock ( )
onlineKernels = [ ] * OnlineKernel { }
for _ , kernel := range data [ "kernels" ] . ( [ ] interface { } ) {
kernelMap := kernel . ( map [ string ] interface { } )
onlineKernels = append ( onlineKernels , & OnlineKernel {
ID : kernelMap [ "id" ] . ( string ) ,
Hostname : kernelMap [ "hostname" ] . ( string ) ,
OS : kernelMap [ "os" ] . ( string ) ,
Ver : kernelMap [ "ver" ] . ( string ) ,
} )
}
onlineKernelsLock . Unlock ( )
}
2023-06-11 10:13:39 +08:00
}
} ( )
}
2023-06-12 12:04:28 +08:00
var KernelID = gulu . Rand . String ( 7 )
2023-06-11 22:53:37 +08:00
2023-06-11 10:13:39 +08:00
func dialSyncWebSocket ( ) ( c * websocket . Conn , err error ) {
2023-06-20 11:48:44 +08:00
endpoint := util . GetCloudWebSocketServer ( ) + "/apis/siyuan/dejavu/ws"
2023-06-11 10:13:39 +08:00
header := http . Header {
2023-10-29 09:54:26 +08:00
"User-Agent" : [ ] string { util . UserAgent } ,
2023-12-08 21:46:46 +08:00
"x-siyuan-uid" : [ ] string { Conf . GetUser ( ) . UserId } ,
2023-06-12 12:04:28 +08:00
"x-siyuan-kernel" : [ ] string { KernelID } ,
2023-06-11 22:53:37 +08:00
"x-siyuan-ver" : [ ] string { util . Ver } ,
"x-siyuan-os" : [ ] string { runtime . GOOS } ,
"x-siyuan-hostname" : [ ] string { util . GetDeviceName ( ) } ,
"x-siyuan-repo" : [ ] string { Conf . Sync . CloudName } ,
2023-06-11 10:13:39 +08:00
}
c , _ , err = websocket . DefaultDialer . Dial ( endpoint , header )
2024-09-04 04:40:50 +03:00
if err == nil {
2023-12-08 13:15:52 +08:00
closedSyncWebSocket . Store ( false )
2023-06-11 23:41:26 +08:00
}
2023-06-11 10:13:39 +08:00
return
}