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"
2022-07-19 09:43:24 +08:00
"path"
2022-05-26 15:18:53 +08:00
"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"
"github.com/88250/gulu"
2023-05-15 14:56:12 +08:00
"github.com/88250/lute/html"
2022-05-26 15:18:53 +08:00
"github.com/dustin/go-humanize"
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"
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
if nil != err {
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
if nil != err {
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
if nil != err {
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 ( )
isSyncing = true
}
func unlockSync ( ) {
isSyncing = false
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
}
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
}
2023-12-08 13:05:50 +08:00
lockSync ( )
defer unlockSync ( )
2022-09-01 23:55:44 +08:00
2022-05-26 15:18:53 +08:00
if exit {
ExitSyncSucc = 0
2023-01-20 10:01:10 +08:00
logging . LogInfof ( "sync before exit" )
util . PushMsg ( Conf . Language ( 81 ) , 1000 * 60 * 15 )
}
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
if nil != err {
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 {
2023-06-11 22:53:37 +08:00
// 如果处于自动同步模式且不是又 WS 触发的同步,则通知其他设备上的内核进行同步
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
}
2023-07-21 20:45:56 +08:00
case conf . ProviderWebDAV , conf . ProviderS3 :
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-12-08 13:05:50 +08:00
if isSyncing {
2023-01-20 10:01:10 +08:00
logging . LogWarnf ( "sync is in progress" )
2023-04-24 15:50:02 +08:00
planSyncAfter ( fixSyncInterval )
2023-01-20 10:01:10 +08:00
return false
2022-05-26 15:18:53 +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..." )
2022-07-18 00:01:58 +08:00
msg := fmt . Sprintf ( Conf . Language ( 35 ) )
util . PushStatusBar ( msg )
2022-07-15 22:48:32 +08:00
2023-02-10 14:28:10 +08:00
luteEngine := util . NewLute ( )
2022-07-18 10:14:03 +08:00
// 先执行 remove, 否则移动文档时 upsert 会被忽略,导致未被索引
2023-12-08 13:05:50 +08:00
bootProgressPart := int32 ( 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" )
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 {
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 )
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 )
2023-12-08 13:05:50 +08:00
bootProgressPart = int32 ( 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 )
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
}
2023-01-25 20:46:17 +08:00
treenode . IndexBlockTree ( tree )
2022-06-30 22:40:43 +08:00
sql . UpsertTreeQueue ( tree )
2023-04-25 18:31:22 +08:00
upsertRootIDs = append ( upsertRootIDs , tree . Root . ID )
2022-06-30 22:40:43 +08:00
}
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
}
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 )
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 )
2022-11-24 22:08:58 +08:00
2022-11-11 15:33:05 +08:00
Conf . Sync . WebDAV = webdav
Conf . Save ( )
return
}
2023-12-08 13:05:50 +08:00
var (
syncLock = sync . Mutex { }
isSyncing bool
)
2022-05-26 15:18:53 +08:00
func CreateCloudSyncDir ( name string ) ( err error ) {
2022-11-11 19:45:41 +08:00
if conf . ProviderSiYuan != Conf . Sync . Provider {
err = errors . New ( Conf . Language ( 131 ) )
return
}
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-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 ( )
2022-07-13 17:14:54 +08:00
if nil != err {
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 )
2022-11-11 19:45:41 +08:00
if nil != err {
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 ) {
2022-11-11 19:45:41 +08:00
if conf . ProviderSiYuan != Conf . Sync . Provider {
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 ( )
2022-07-13 17:44:28 +08:00
if nil != err {
return
2022-07-05 00:30:47 +08:00
}
2022-10-31 20:59:00 +08:00
err = repo . RemoveCloudRepo ( name )
2022-05-26 15:18:53 +08:00
if nil != err {
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 ( )
2022-07-13 17:44:28 +08:00
if nil != err {
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 ( )
2022-07-05 00:30:47 +08:00
if nil != err {
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 {
sync . HSize = humanize . Bytes ( uint64 ( dirSize ) )
}
syncDirs = append ( syncDirs , sync )
}
hSize = "-"
if conf . ProviderSiYuan == Conf . Sync . Provider {
hSize = humanize . Bytes ( uint64 ( size ) )
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-02-03 20:07:54 +08:00
} else if errors . Is ( err , dejavu . ErrRepoFatalErr ) {
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 )
2023-02-03 20:07:54 +08:00
} else {
msgLowerCase := strings . ToLower ( msg )
if strings . Contains ( msgLowerCase , "permission denied" ) || strings . Contains ( msg , "access is denied" ) {
msg = Conf . Language ( 33 )
} 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 )
2023-03-31 10:15:22 +08:00
} else if strings . Contains ( msgLowerCase , "connection was" ) || strings . Contains ( msgLowerCase , "reset by peer" ) || strings . Contains ( msgLowerCase , "refused" ) || strings . Contains ( msgLowerCase , "socket" ) || strings . Contains ( msgLowerCase , "closed idle connection" ) || strings . Contains ( msgLowerCase , "eof" ) {
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
}
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 ( ) {
2023-12-08 13:05:50 +08:00
syncSameCount . Store ( 0 )
2022-07-02 19:36:17 +08:00
planSyncAfter ( 30 * 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 ) {
2023-06-20 11:48:44 +08:00
checkURL := util . GetCloudSyncServer ( )
2023-04-05 19:54:13 +08:00
skipTlsVerify := false
2023-03-29 14:43:02 +08:00
switch Conf . Sync . Provider {
case conf . ProviderSiYuan :
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
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
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
2023-04-05 19:54:13 +08:00
if ret = util . IsOnline ( checkURL , skipTlsVerify ) ; ! 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-06-11 23:41:26 +08:00
var closedSyncWebSocket = false
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-06-11 23:41:26 +08:00
closedSyncWebSocket = 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 )
if closedSyncWebSocket {
return
}
2023-06-11 10:13:39 +08:00
reconnected := false
for retries := 0 ; retries < 7 ; retries ++ {
time . Sleep ( 7 * time . Second )
2023-06-21 15:31:28 +08:00
if nil == Conf . User {
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" :
2023-11-25 17:13:43 +08:00
syncData ( false , false )
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-06-11 22:53:37 +08:00
"x-siyuan-uid" : [ ] string { Conf . User . 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 )
2023-06-11 23:41:26 +08:00
if nil == err {
closedSyncWebSocket = false
}
2023-06-11 10:13:39 +08:00
return
}