sync.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package api
  17. import (
  18. "encoding/hex"
  19. "github.com/siyuan-note/logging"
  20. "io"
  21. "net/http"
  22. "os"
  23. "path/filepath"
  24. "time"
  25. "github.com/88250/gulu"
  26. "github.com/gin-gonic/gin"
  27. "github.com/siyuan-note/siyuan/kernel/conf"
  28. "github.com/siyuan-note/siyuan/kernel/model"
  29. "github.com/siyuan-note/siyuan/kernel/util"
  30. )
  31. func importSyncProviderWebDAV(c *gin.Context) {
  32. ret := gulu.Ret.NewResult()
  33. defer c.JSON(200, ret)
  34. form, err := c.MultipartForm()
  35. if nil != err {
  36. logging.LogErrorf("read upload file failed: %s", err)
  37. ret.Code = -1
  38. ret.Msg = err.Error()
  39. return
  40. }
  41. files := form.File["file"]
  42. if 1 != len(files) {
  43. ret.Code = -1
  44. ret.Msg = "invalid upload file"
  45. return
  46. }
  47. f := files[0]
  48. fh, err := f.Open()
  49. if nil != err {
  50. logging.LogErrorf("read upload file failed: %s", err)
  51. ret.Code = -1
  52. ret.Msg = err.Error()
  53. return
  54. }
  55. data, err := io.ReadAll(fh)
  56. fh.Close()
  57. if nil != err {
  58. logging.LogErrorf("read upload file failed: %s", err)
  59. ret.Code = -1
  60. ret.Msg = err.Error()
  61. return
  62. }
  63. tmpDir := filepath.Join(util.TempDir, "import")
  64. if err = os.MkdirAll(tmpDir, 0755); nil != err {
  65. logging.LogErrorf("import WebDAV provider failed: %s", err)
  66. ret.Code = -1
  67. ret.Msg = err.Error()
  68. return
  69. }
  70. tmp := filepath.Join(tmpDir, f.Filename)
  71. if err = os.WriteFile(tmp, data, 0644); nil != err {
  72. logging.LogErrorf("import WebDAV provider failed: %s", err)
  73. ret.Code = -1
  74. ret.Msg = err.Error()
  75. return
  76. }
  77. if err = gulu.Zip.Unzip(tmp, tmpDir); nil != err {
  78. logging.LogErrorf("import WebDAV provider failed: %s", err)
  79. ret.Code = -1
  80. ret.Msg = err.Error()
  81. return
  82. }
  83. tmp = filepath.Join(tmpDir, f.Filename[:len(f.Filename)-4])
  84. data, err = os.ReadFile(tmp)
  85. if nil != err {
  86. logging.LogErrorf("import WebDAV provider failed: %s", err)
  87. ret.Code = -1
  88. ret.Msg = err.Error()
  89. return
  90. }
  91. data = util.AESDecrypt(string(data))
  92. data, _ = hex.DecodeString(string(data))
  93. webdav := &conf.WebDAV{}
  94. if err = gulu.JSON.UnmarshalJSON(data, webdav); nil != err {
  95. logging.LogErrorf("import WebDAV provider failed: %s", err)
  96. ret.Code = -1
  97. ret.Msg = err.Error()
  98. return
  99. }
  100. err = model.SetSyncProviderWebDAV(webdav)
  101. if nil != err {
  102. logging.LogErrorf("import WebDAV provider failed: %s", err)
  103. ret.Code = -1
  104. ret.Msg = err.Error()
  105. return
  106. }
  107. ret.Data = map[string]interface{}{
  108. "webdav": model.Conf.Sync.WebDAV,
  109. }
  110. }
  111. func exportSyncProviderWebDAV(c *gin.Context) {
  112. ret := gulu.Ret.NewResult()
  113. defer c.JSON(http.StatusOK, ret)
  114. name := "siyuan-webdav-" + time.Now().Format("20060102150405") + ".json"
  115. tmpDir := filepath.Join(util.TempDir, "export")
  116. if err := os.MkdirAll(tmpDir, 0755); nil != err {
  117. logging.LogErrorf("export WebDAV provider failed: %s", err)
  118. ret.Code = -1
  119. ret.Msg = err.Error()
  120. return
  121. }
  122. webdav := model.Conf.Sync.WebDAV
  123. if nil == webdav {
  124. webdav = &conf.WebDAV{}
  125. }
  126. data, err := gulu.JSON.MarshalJSON(model.Conf.Sync.WebDAV)
  127. if nil != err {
  128. logging.LogErrorf("export WebDAV provider failed: %s", err)
  129. ret.Code = -1
  130. ret.Msg = err.Error()
  131. return
  132. }
  133. dataStr := util.AESEncrypt(string(data))
  134. tmp := filepath.Join(tmpDir, name)
  135. if err = os.WriteFile(tmp, []byte(dataStr), 0644); nil != err {
  136. logging.LogErrorf("export WebDAV provider failed: %s", err)
  137. ret.Code = -1
  138. ret.Msg = err.Error()
  139. return
  140. }
  141. zipFile, err := gulu.Zip.Create(tmp + ".zip")
  142. if nil != err {
  143. logging.LogErrorf("export WebDAV provider failed: %s", err)
  144. ret.Code = -1
  145. ret.Msg = err.Error()
  146. return
  147. }
  148. if err = zipFile.AddEntry(name, tmp); nil != err {
  149. logging.LogErrorf("export WebDAV provider failed: %s", err)
  150. ret.Code = -1
  151. ret.Msg = err.Error()
  152. return
  153. }
  154. if err = zipFile.Close(); nil != err {
  155. logging.LogErrorf("export WebDAV provider failed: %s", err)
  156. ret.Code = -1
  157. ret.Msg = err.Error()
  158. return
  159. }
  160. zipPath := "/export/" + name + ".zip"
  161. ret.Data = map[string]interface{}{
  162. "name": name,
  163. "zip": zipPath,
  164. }
  165. }
  166. func importSyncProviderS3(c *gin.Context) {
  167. ret := gulu.Ret.NewResult()
  168. defer c.JSON(200, ret)
  169. form, err := c.MultipartForm()
  170. if nil != err {
  171. logging.LogErrorf("read upload file failed: %s", err)
  172. ret.Code = -1
  173. ret.Msg = err.Error()
  174. return
  175. }
  176. files := form.File["file"]
  177. if 1 != len(files) {
  178. ret.Code = -1
  179. ret.Msg = "invalid upload file"
  180. return
  181. }
  182. f := files[0]
  183. fh, err := f.Open()
  184. if nil != err {
  185. logging.LogErrorf("read upload file failed: %s", err)
  186. ret.Code = -1
  187. ret.Msg = err.Error()
  188. return
  189. }
  190. data, err := io.ReadAll(fh)
  191. fh.Close()
  192. if nil != err {
  193. logging.LogErrorf("read upload file failed: %s", err)
  194. ret.Code = -1
  195. ret.Msg = err.Error()
  196. return
  197. }
  198. importDir := filepath.Join(util.TempDir, "import")
  199. if err = os.MkdirAll(importDir, 0755); nil != err {
  200. logging.LogErrorf("import S3 provider failed: %s", err)
  201. ret.Code = -1
  202. ret.Msg = err.Error()
  203. return
  204. }
  205. tmp := filepath.Join(importDir, f.Filename)
  206. if err = os.WriteFile(tmp, data, 0644); nil != err {
  207. logging.LogErrorf("import S3 provider failed: %s", err)
  208. ret.Code = -1
  209. ret.Msg = err.Error()
  210. return
  211. }
  212. tmpDir := filepath.Join(importDir, "s3")
  213. if err = gulu.Zip.Unzip(tmp, tmpDir); nil != err {
  214. logging.LogErrorf("import S3 provider failed: %s", err)
  215. ret.Code = -1
  216. ret.Msg = err.Error()
  217. return
  218. }
  219. entries, err := os.ReadDir(tmpDir)
  220. if nil != err {
  221. logging.LogErrorf("import S3 provider failed: %s", err)
  222. ret.Code = -1
  223. ret.Msg = err.Error()
  224. return
  225. }
  226. if 1 != len(entries) {
  227. logging.LogErrorf("invalid S3 provider package")
  228. ret.Code = -1
  229. ret.Msg = "invalid S3 provider package"
  230. return
  231. }
  232. tmp = filepath.Join(tmpDir, entries[0].Name())
  233. data, err = os.ReadFile(tmp)
  234. if nil != err {
  235. logging.LogErrorf("import S3 provider failed: %s", err)
  236. ret.Code = -1
  237. ret.Msg = err.Error()
  238. return
  239. }
  240. data = util.AESDecrypt(string(data))
  241. data, _ = hex.DecodeString(string(data))
  242. s3 := &conf.S3{}
  243. if err = gulu.JSON.UnmarshalJSON(data, s3); nil != err {
  244. logging.LogErrorf("import S3 provider failed: %s", err)
  245. ret.Code = -1
  246. ret.Msg = err.Error()
  247. return
  248. }
  249. err = model.SetSyncProviderS3(s3)
  250. if nil != err {
  251. logging.LogErrorf("import S3 provider failed: %s", err)
  252. ret.Code = -1
  253. ret.Msg = err.Error()
  254. return
  255. }
  256. ret.Data = map[string]interface{}{
  257. "s3": model.Conf.Sync.S3,
  258. }
  259. }
  260. func exportSyncProviderS3(c *gin.Context) {
  261. ret := gulu.Ret.NewResult()
  262. defer c.JSON(http.StatusOK, ret)
  263. name := "siyuan-s3-" + time.Now().Format("20060102150405") + ".json"
  264. tmpDir := filepath.Join(util.TempDir, "export")
  265. if err := os.MkdirAll(tmpDir, 0755); nil != err {
  266. logging.LogErrorf("export S3 provider failed: %s", err)
  267. ret.Code = -1
  268. ret.Msg = err.Error()
  269. return
  270. }
  271. s3 := model.Conf.Sync.S3
  272. if nil == s3 {
  273. s3 = &conf.S3{}
  274. }
  275. data, err := gulu.JSON.MarshalJSON(model.Conf.Sync.S3)
  276. if nil != err {
  277. logging.LogErrorf("export S3 provider failed: %s", err)
  278. ret.Code = -1
  279. ret.Msg = err.Error()
  280. return
  281. }
  282. dataStr := util.AESEncrypt(string(data))
  283. tmp := filepath.Join(tmpDir, name)
  284. if err = os.WriteFile(tmp, []byte(dataStr), 0644); nil != err {
  285. logging.LogErrorf("export S3 provider failed: %s", err)
  286. ret.Code = -1
  287. ret.Msg = err.Error()
  288. return
  289. }
  290. zipFile, err := gulu.Zip.Create(tmp + ".zip")
  291. if nil != err {
  292. logging.LogErrorf("export S3 provider failed: %s", err)
  293. ret.Code = -1
  294. ret.Msg = err.Error()
  295. return
  296. }
  297. if err = zipFile.AddEntry(name, tmp); nil != err {
  298. logging.LogErrorf("export S3 provider failed: %s", err)
  299. ret.Code = -1
  300. ret.Msg = err.Error()
  301. return
  302. }
  303. if err = zipFile.Close(); nil != err {
  304. logging.LogErrorf("export S3 provider failed: %s", err)
  305. ret.Code = -1
  306. ret.Msg = err.Error()
  307. return
  308. }
  309. zipPath := "/export/" + name + ".zip"
  310. ret.Data = map[string]interface{}{
  311. "name": name,
  312. "zip": zipPath,
  313. }
  314. }
  315. func getSyncInfo(c *gin.Context) {
  316. ret := gulu.Ret.NewResult()
  317. defer c.JSON(http.StatusOK, ret)
  318. stat := model.Conf.Sync.Stat
  319. if !model.Conf.Sync.Enabled {
  320. stat = model.Conf.Language(53)
  321. }
  322. ret.Data = map[string]interface{}{
  323. "synced": model.Conf.Sync.Synced,
  324. "stat": stat,
  325. "kernels": model.GetOnlineKernels(),
  326. "kernel": model.KernelID,
  327. }
  328. }
  329. func getBootSync(c *gin.Context) {
  330. ret := gulu.Ret.NewResult()
  331. defer c.JSON(http.StatusOK, ret)
  332. if !model.IsAdminRoleContext(c) {
  333. return
  334. }
  335. if model.Conf.Sync.Enabled && 1 == model.BootSyncSucc {
  336. ret.Code = 1
  337. ret.Msg = model.Conf.Language(17)
  338. return
  339. }
  340. }
  341. func performSync(c *gin.Context) {
  342. ret := gulu.Ret.NewResult()
  343. defer c.JSON(http.StatusOK, ret)
  344. arg, ok := util.JsonArg(c, ret)
  345. if !ok {
  346. return
  347. }
  348. // Android 端前后台切换时自动触发同步 https://github.com/siyuan-note/siyuan/issues/7122
  349. var mobileSwitch bool
  350. if mobileSwitchArg := arg["mobileSwitch"]; nil != mobileSwitchArg {
  351. mobileSwitch = mobileSwitchArg.(bool)
  352. }
  353. if mobileSwitch {
  354. if nil == model.Conf.GetUser() || !model.Conf.Sync.Enabled {
  355. return
  356. }
  357. }
  358. if 3 != model.Conf.Sync.Mode {
  359. model.SyncData(true)
  360. return
  361. }
  362. // 云端同步模式支持 `完全手动同步` 模式 https://github.com/siyuan-note/siyuan/issues/7295
  363. uploadArg := arg["upload"]
  364. if nil == uploadArg {
  365. // 必须传入同步方向,未传的话不执行同步
  366. return
  367. }
  368. upload := uploadArg.(bool)
  369. if upload {
  370. model.SyncDataUpload()
  371. } else {
  372. model.SyncDataDownload()
  373. }
  374. }
  375. func performBootSync(c *gin.Context) {
  376. ret := gulu.Ret.NewResult()
  377. defer c.JSON(http.StatusOK, ret)
  378. model.BootSyncData()
  379. ret.Code = model.BootSyncSucc
  380. }
  381. func listCloudSyncDir(c *gin.Context) {
  382. ret := gulu.Ret.NewResult()
  383. defer c.JSON(http.StatusOK, ret)
  384. syncDirs, hSize, err := model.ListCloudSyncDir()
  385. if nil != err {
  386. ret.Code = 1
  387. ret.Msg = err.Error()
  388. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  389. return
  390. }
  391. ret.Data = map[string]interface{}{
  392. "syncDirs": syncDirs,
  393. "hSize": hSize,
  394. "checkedSyncDir": model.Conf.Sync.CloudName,
  395. }
  396. }
  397. func removeCloudSyncDir(c *gin.Context) {
  398. ret := gulu.Ret.NewResult()
  399. defer c.JSON(http.StatusOK, ret)
  400. arg, ok := util.JsonArg(c, ret)
  401. if !ok {
  402. return
  403. }
  404. name := arg["name"].(string)
  405. err := model.RemoveCloudSyncDir(name)
  406. if nil != err {
  407. ret.Code = -1
  408. ret.Msg = err.Error()
  409. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  410. return
  411. }
  412. ret.Data = model.Conf.Sync.CloudName
  413. }
  414. func createCloudSyncDir(c *gin.Context) {
  415. ret := gulu.Ret.NewResult()
  416. defer c.JSON(http.StatusOK, ret)
  417. arg, ok := util.JsonArg(c, ret)
  418. if !ok {
  419. return
  420. }
  421. name := arg["name"].(string)
  422. err := model.CreateCloudSyncDir(name)
  423. if nil != err {
  424. ret.Code = -1
  425. ret.Msg = err.Error()
  426. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  427. return
  428. }
  429. }
  430. func setSyncGenerateConflictDoc(c *gin.Context) {
  431. ret := gulu.Ret.NewResult()
  432. defer c.JSON(http.StatusOK, ret)
  433. arg, ok := util.JsonArg(c, ret)
  434. if !ok {
  435. return
  436. }
  437. enabled := arg["enabled"].(bool)
  438. model.SetSyncGenerateConflictDoc(enabled)
  439. }
  440. func setSyncEnable(c *gin.Context) {
  441. ret := gulu.Ret.NewResult()
  442. defer c.JSON(http.StatusOK, ret)
  443. arg, ok := util.JsonArg(c, ret)
  444. if !ok {
  445. return
  446. }
  447. enabled := arg["enabled"].(bool)
  448. model.SetSyncEnable(enabled)
  449. }
  450. func setSyncPerception(c *gin.Context) {
  451. ret := gulu.Ret.NewResult()
  452. defer c.JSON(http.StatusOK, ret)
  453. arg, ok := util.JsonArg(c, ret)
  454. if !ok {
  455. return
  456. }
  457. enabled := arg["enabled"].(bool)
  458. model.SetSyncPerception(enabled)
  459. }
  460. func setSyncMode(c *gin.Context) {
  461. ret := gulu.Ret.NewResult()
  462. defer c.JSON(http.StatusOK, ret)
  463. arg, ok := util.JsonArg(c, ret)
  464. if !ok {
  465. return
  466. }
  467. mode := int(arg["mode"].(float64))
  468. model.SetSyncMode(mode)
  469. }
  470. func setSyncProvider(c *gin.Context) {
  471. ret := gulu.Ret.NewResult()
  472. defer c.JSON(http.StatusOK, ret)
  473. arg, ok := util.JsonArg(c, ret)
  474. if !ok {
  475. return
  476. }
  477. provider := int(arg["provider"].(float64))
  478. err := model.SetSyncProvider(provider)
  479. if nil != err {
  480. ret.Code = -1
  481. ret.Msg = err.Error()
  482. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  483. return
  484. }
  485. }
  486. func setSyncProviderS3(c *gin.Context) {
  487. ret := gulu.Ret.NewResult()
  488. defer c.JSON(http.StatusOK, ret)
  489. arg, ok := util.JsonArg(c, ret)
  490. if !ok {
  491. return
  492. }
  493. s3Arg := arg["s3"].(interface{})
  494. data, err := gulu.JSON.MarshalJSON(s3Arg)
  495. if nil != err {
  496. ret.Code = -1
  497. ret.Msg = err.Error()
  498. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  499. return
  500. }
  501. s3 := &conf.S3{}
  502. if err = gulu.JSON.UnmarshalJSON(data, s3); nil != err {
  503. ret.Code = -1
  504. ret.Msg = err.Error()
  505. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  506. return
  507. }
  508. err = model.SetSyncProviderS3(s3)
  509. if nil != err {
  510. ret.Code = -1
  511. ret.Msg = err.Error()
  512. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  513. return
  514. }
  515. }
  516. func setSyncProviderWebDAV(c *gin.Context) {
  517. ret := gulu.Ret.NewResult()
  518. defer c.JSON(http.StatusOK, ret)
  519. arg, ok := util.JsonArg(c, ret)
  520. if !ok {
  521. return
  522. }
  523. webdavArg := arg["webdav"].(interface{})
  524. data, err := gulu.JSON.MarshalJSON(webdavArg)
  525. if nil != err {
  526. ret.Code = -1
  527. ret.Msg = err.Error()
  528. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  529. return
  530. }
  531. webdav := &conf.WebDAV{}
  532. if err = gulu.JSON.UnmarshalJSON(data, webdav); nil != err {
  533. ret.Code = -1
  534. ret.Msg = err.Error()
  535. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  536. return
  537. }
  538. err = model.SetSyncProviderWebDAV(webdav)
  539. if nil != err {
  540. ret.Code = -1
  541. ret.Msg = err.Error()
  542. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  543. return
  544. }
  545. }
  546. func setCloudSyncDir(c *gin.Context) {
  547. ret := gulu.Ret.NewResult()
  548. defer c.JSON(http.StatusOK, ret)
  549. arg, ok := util.JsonArg(c, ret)
  550. if !ok {
  551. return
  552. }
  553. name := arg["name"].(string)
  554. model.SetCloudSyncDir(name)
  555. }