A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 749 lines 17 kB view raw
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 17package api 18 19import ( 20 "encoding/hex" 21 "io" 22 "net/http" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 "github.com/siyuan-note/logging" 29 30 "github.com/88250/gulu" 31 "github.com/gin-gonic/gin" 32 "github.com/siyuan-note/siyuan/kernel/conf" 33 "github.com/siyuan-note/siyuan/kernel/model" 34 "github.com/siyuan-note/siyuan/kernel/util" 35) 36 37func importSyncProviderWebDAV(c *gin.Context) { 38 ret := gulu.Ret.NewResult() 39 defer c.JSON(200, ret) 40 41 form, err := c.MultipartForm() 42 if err != nil { 43 logging.LogErrorf("read upload file failed: %s", err) 44 ret.Code = -1 45 ret.Msg = err.Error() 46 return 47 } 48 49 files := form.File["file"] 50 if 1 != len(files) { 51 ret.Code = -1 52 ret.Msg = "invalid upload file" 53 return 54 } 55 56 f := files[0] 57 fh, err := f.Open() 58 if err != nil { 59 logging.LogErrorf("read upload file failed: %s", err) 60 ret.Code = -1 61 ret.Msg = err.Error() 62 return 63 } 64 65 data, err := io.ReadAll(fh) 66 fh.Close() 67 if err != nil { 68 logging.LogErrorf("read upload file failed: %s", err) 69 ret.Code = -1 70 ret.Msg = err.Error() 71 return 72 } 73 74 importDir := filepath.Join(util.TempDir, "import") 75 if err = os.MkdirAll(importDir, 0755); err != nil { 76 logging.LogErrorf("import WebDAV provider failed: %s", err) 77 ret.Code = -1 78 ret.Msg = err.Error() 79 return 80 } 81 82 tmp := filepath.Join(importDir, f.Filename) 83 if err = os.WriteFile(tmp, data, 0644); err != nil { 84 logging.LogErrorf("import WebDAV provider failed: %s", err) 85 ret.Code = -1 86 ret.Msg = err.Error() 87 return 88 } 89 90 tmpDir := filepath.Join(importDir, "webdav") 91 os.RemoveAll(tmpDir) 92 if strings.HasSuffix(strings.ToLower(tmp), ".zip") { 93 if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil { 94 logging.LogErrorf("import WebDAV provider failed: %s", err) 95 ret.Code = -1 96 ret.Msg = err.Error() 97 return 98 } 99 } else if strings.HasSuffix(strings.ToLower(tmp), ".json") { 100 if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil { 101 logging.LogErrorf("import WebDAV provider failed: %s", err) 102 ret.Code = -1 103 ret.Msg = err.Error() 104 } 105 } else { 106 logging.LogErrorf("invalid WebDAV provider package") 107 ret.Code = -1 108 ret.Msg = "invalid WebDAV provider package" 109 return 110 } 111 112 entries, err := os.ReadDir(tmpDir) 113 if err != nil { 114 logging.LogErrorf("import WebDAV provider failed: %s", err) 115 ret.Code = -1 116 ret.Msg = err.Error() 117 return 118 } 119 120 if 1 != len(entries) { 121 logging.LogErrorf("invalid WebDAV provider package") 122 ret.Code = -1 123 ret.Msg = "invalid WebDAV provider package" 124 return 125 } 126 127 tmp = filepath.Join(tmpDir, entries[0].Name()) 128 data, err = os.ReadFile(tmp) 129 if err != nil { 130 logging.LogErrorf("import WebDAV provider failed: %s", err) 131 ret.Code = -1 132 ret.Msg = err.Error() 133 return 134 } 135 136 data = util.AESDecrypt(string(data)) 137 data, _ = hex.DecodeString(string(data)) 138 webdav := &conf.WebDAV{} 139 if err = gulu.JSON.UnmarshalJSON(data, webdav); err != nil { 140 logging.LogErrorf("import WebDAV provider failed: %s", err) 141 ret.Code = -1 142 ret.Msg = err.Error() 143 return 144 } 145 146 err = model.SetSyncProviderWebDAV(webdav) 147 if err != nil { 148 logging.LogErrorf("import WebDAV provider failed: %s", err) 149 ret.Code = -1 150 ret.Msg = err.Error() 151 return 152 } 153 154 ret.Data = map[string]interface{}{ 155 "webdav": model.Conf.Sync.WebDAV, 156 } 157} 158 159func exportSyncProviderWebDAV(c *gin.Context) { 160 ret := gulu.Ret.NewResult() 161 defer c.JSON(http.StatusOK, ret) 162 163 name := "siyuan-webdav-" + time.Now().Format("20060102150405") + ".json" 164 tmpDir := filepath.Join(util.TempDir, "export") 165 if err := os.MkdirAll(tmpDir, 0755); err != nil { 166 logging.LogErrorf("export WebDAV provider failed: %s", err) 167 ret.Code = -1 168 ret.Msg = err.Error() 169 return 170 } 171 172 webdav := model.Conf.Sync.WebDAV 173 if nil == webdav { 174 webdav = &conf.WebDAV{} 175 } 176 177 data, err := gulu.JSON.MarshalJSON(model.Conf.Sync.WebDAV) 178 if err != nil { 179 logging.LogErrorf("export WebDAV provider failed: %s", err) 180 ret.Code = -1 181 ret.Msg = err.Error() 182 return 183 } 184 185 dataStr := util.AESEncrypt(string(data)) 186 tmp := filepath.Join(tmpDir, name) 187 if err = os.WriteFile(tmp, []byte(dataStr), 0644); err != nil { 188 logging.LogErrorf("export WebDAV provider failed: %s", err) 189 ret.Code = -1 190 ret.Msg = err.Error() 191 return 192 } 193 194 zipFile, err := gulu.Zip.Create(tmp + ".zip") 195 if err != nil { 196 logging.LogErrorf("export WebDAV provider failed: %s", err) 197 ret.Code = -1 198 ret.Msg = err.Error() 199 return 200 } 201 202 if err = zipFile.AddEntry(name, tmp); err != nil { 203 logging.LogErrorf("export WebDAV provider failed: %s", err) 204 ret.Code = -1 205 ret.Msg = err.Error() 206 return 207 } 208 209 if err = zipFile.Close(); err != nil { 210 logging.LogErrorf("export WebDAV provider failed: %s", err) 211 ret.Code = -1 212 ret.Msg = err.Error() 213 return 214 } 215 216 zipPath := "/export/" + name + ".zip" 217 ret.Data = map[string]interface{}{ 218 "name": name, 219 "zip": zipPath, 220 } 221} 222 223func importSyncProviderS3(c *gin.Context) { 224 ret := gulu.Ret.NewResult() 225 defer c.JSON(200, ret) 226 227 form, err := c.MultipartForm() 228 if err != nil { 229 logging.LogErrorf("read upload file failed: %s", err) 230 ret.Code = -1 231 ret.Msg = err.Error() 232 return 233 } 234 235 files := form.File["file"] 236 if 1 != len(files) { 237 ret.Code = -1 238 ret.Msg = "invalid upload file" 239 return 240 } 241 242 f := files[0] 243 fh, err := f.Open() 244 if err != nil { 245 logging.LogErrorf("read upload file failed: %s", err) 246 ret.Code = -1 247 ret.Msg = err.Error() 248 return 249 } 250 251 data, err := io.ReadAll(fh) 252 fh.Close() 253 if err != nil { 254 logging.LogErrorf("read upload file failed: %s", err) 255 ret.Code = -1 256 ret.Msg = err.Error() 257 return 258 } 259 260 importDir := filepath.Join(util.TempDir, "import") 261 if err = os.MkdirAll(importDir, 0755); err != nil { 262 logging.LogErrorf("import S3 provider failed: %s", err) 263 ret.Code = -1 264 ret.Msg = err.Error() 265 return 266 } 267 268 tmp := filepath.Join(importDir, f.Filename) 269 if err = os.WriteFile(tmp, data, 0644); err != nil { 270 logging.LogErrorf("import S3 provider failed: %s", err) 271 ret.Code = -1 272 ret.Msg = err.Error() 273 return 274 } 275 276 tmpDir := filepath.Join(importDir, "s3") 277 os.RemoveAll(tmpDir) 278 if strings.HasSuffix(strings.ToLower(tmp), ".zip") { 279 if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil { 280 logging.LogErrorf("import S3 provider failed: %s", err) 281 ret.Code = -1 282 ret.Msg = err.Error() 283 return 284 } 285 } else if strings.HasSuffix(strings.ToLower(tmp), ".json") { 286 if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil { 287 logging.LogErrorf("import S3 provider failed: %s", err) 288 ret.Code = -1 289 ret.Msg = err.Error() 290 } 291 } else { 292 logging.LogErrorf("invalid S3 provider package") 293 ret.Code = -1 294 ret.Msg = "invalid S3 provider package" 295 return 296 } 297 298 entries, err := os.ReadDir(tmpDir) 299 if err != nil { 300 logging.LogErrorf("import S3 provider failed: %s", err) 301 ret.Code = -1 302 ret.Msg = err.Error() 303 return 304 } 305 306 if 1 != len(entries) { 307 logging.LogErrorf("invalid S3 provider package") 308 ret.Code = -1 309 ret.Msg = "invalid S3 provider package" 310 return 311 } 312 313 tmp = filepath.Join(tmpDir, entries[0].Name()) 314 data, err = os.ReadFile(tmp) 315 if err != nil { 316 logging.LogErrorf("import S3 provider failed: %s", err) 317 ret.Code = -1 318 ret.Msg = err.Error() 319 return 320 } 321 322 data = util.AESDecrypt(string(data)) 323 data, _ = hex.DecodeString(string(data)) 324 s3 := &conf.S3{} 325 if err = gulu.JSON.UnmarshalJSON(data, s3); err != nil { 326 logging.LogErrorf("import S3 provider failed: %s", err) 327 ret.Code = -1 328 ret.Msg = err.Error() 329 return 330 } 331 332 err = model.SetSyncProviderS3(s3) 333 if err != nil { 334 logging.LogErrorf("import S3 provider failed: %s", err) 335 ret.Code = -1 336 ret.Msg = err.Error() 337 return 338 } 339 340 ret.Data = map[string]interface{}{ 341 "s3": model.Conf.Sync.S3, 342 } 343} 344 345func exportSyncProviderS3(c *gin.Context) { 346 ret := gulu.Ret.NewResult() 347 defer c.JSON(http.StatusOK, ret) 348 349 name := "siyuan-s3-" + time.Now().Format("20060102150405") + ".json" 350 tmpDir := filepath.Join(util.TempDir, "export") 351 if err := os.MkdirAll(tmpDir, 0755); err != nil { 352 logging.LogErrorf("export S3 provider failed: %s", err) 353 ret.Code = -1 354 ret.Msg = err.Error() 355 return 356 } 357 358 s3 := model.Conf.Sync.S3 359 if nil == s3 { 360 s3 = &conf.S3{} 361 } 362 363 data, err := gulu.JSON.MarshalJSON(model.Conf.Sync.S3) 364 if err != nil { 365 logging.LogErrorf("export S3 provider failed: %s", err) 366 ret.Code = -1 367 ret.Msg = err.Error() 368 return 369 } 370 371 dataStr := util.AESEncrypt(string(data)) 372 tmp := filepath.Join(tmpDir, name) 373 if err = os.WriteFile(tmp, []byte(dataStr), 0644); err != nil { 374 logging.LogErrorf("export S3 provider failed: %s", err) 375 ret.Code = -1 376 ret.Msg = err.Error() 377 return 378 } 379 380 zipFile, err := gulu.Zip.Create(tmp + ".zip") 381 if err != nil { 382 logging.LogErrorf("export S3 provider failed: %s", err) 383 ret.Code = -1 384 ret.Msg = err.Error() 385 return 386 } 387 388 if err = zipFile.AddEntry(name, tmp); err != nil { 389 logging.LogErrorf("export S3 provider failed: %s", err) 390 ret.Code = -1 391 ret.Msg = err.Error() 392 return 393 } 394 395 if err = zipFile.Close(); err != nil { 396 logging.LogErrorf("export S3 provider failed: %s", err) 397 ret.Code = -1 398 ret.Msg = err.Error() 399 return 400 } 401 402 zipPath := "/export/" + name + ".zip" 403 ret.Data = map[string]interface{}{ 404 "name": name, 405 "zip": zipPath, 406 } 407} 408 409func getSyncInfo(c *gin.Context) { 410 ret := gulu.Ret.NewResult() 411 defer c.JSON(http.StatusOK, ret) 412 413 stat := model.Conf.Sync.Stat 414 if !model.Conf.Sync.Enabled { 415 stat = model.Conf.Language(53) 416 } 417 418 ret.Data = map[string]interface{}{ 419 "synced": model.Conf.Sync.Synced, 420 "stat": stat, 421 "kernels": model.GetOnlineKernels(), 422 "kernel": model.KernelID, 423 } 424} 425 426func getBootSync(c *gin.Context) { 427 ret := gulu.Ret.NewResult() 428 defer c.JSON(http.StatusOK, ret) 429 430 if !model.IsAdminRoleContext(c) { 431 return 432 } 433 434 if model.Conf.Sync.Enabled && 1 == model.BootSyncSucc { 435 ret.Code = 1 436 ret.Msg = model.Conf.Language(17) 437 return 438 } 439} 440 441func performSync(c *gin.Context) { 442 ret := gulu.Ret.NewResult() 443 defer c.JSON(http.StatusOK, ret) 444 445 arg, ok := util.JsonArg(c, ret) 446 if !ok { 447 return 448 } 449 450 // Android 端前后台切换时自动触发同步 https://github.com/siyuan-note/siyuan/issues/7122 451 var mobileSwitch bool 452 if mobileSwitchArg := arg["mobileSwitch"]; nil != mobileSwitchArg { 453 mobileSwitch = mobileSwitchArg.(bool) 454 } 455 if mobileSwitch { 456 if nil == model.Conf.GetUser() || !model.Conf.Sync.Enabled { 457 return 458 } 459 } 460 461 if 3 != model.Conf.Sync.Mode { 462 model.SyncData(true) 463 return 464 } 465 466 // 云端同步模式支持 `完全手动同步` 模式 https://github.com/siyuan-note/siyuan/issues/7295 467 uploadArg := arg["upload"] 468 if nil == uploadArg { 469 // 必须传入同步方向,未传的话不执行同步 470 return 471 } 472 473 upload := uploadArg.(bool) 474 if upload { 475 model.SyncDataUpload() 476 } else { 477 model.SyncDataDownload() 478 } 479} 480 481func performBootSync(c *gin.Context) { 482 ret := gulu.Ret.NewResult() 483 defer c.JSON(http.StatusOK, ret) 484 model.BootSyncData() 485 ret.Code = model.BootSyncSucc 486} 487 488func listCloudSyncDir(c *gin.Context) { 489 ret := gulu.Ret.NewResult() 490 defer c.JSON(http.StatusOK, ret) 491 492 syncDirs, hSize, err := model.ListCloudSyncDir() 493 if err != nil { 494 ret.Code = 1 495 ret.Msg = err.Error() 496 ret.Data = map[string]interface{}{"closeTimeout": 5000} 497 return 498 } 499 500 ret.Data = map[string]interface{}{ 501 "syncDirs": syncDirs, 502 "hSize": hSize, 503 "checkedSyncDir": model.Conf.Sync.CloudName, 504 } 505} 506 507func removeCloudSyncDir(c *gin.Context) { 508 ret := gulu.Ret.NewResult() 509 defer c.JSON(http.StatusOK, ret) 510 511 arg, ok := util.JsonArg(c, ret) 512 if !ok { 513 return 514 } 515 516 name := arg["name"].(string) 517 err := model.RemoveCloudSyncDir(name) 518 if err != nil { 519 ret.Code = -1 520 ret.Msg = err.Error() 521 ret.Data = map[string]interface{}{"closeTimeout": 5000} 522 return 523 } 524 525 ret.Data = model.Conf.Sync.CloudName 526} 527 528func createCloudSyncDir(c *gin.Context) { 529 ret := gulu.Ret.NewResult() 530 defer c.JSON(http.StatusOK, ret) 531 532 arg, ok := util.JsonArg(c, ret) 533 if !ok { 534 return 535 } 536 537 name := arg["name"].(string) 538 err := model.CreateCloudSyncDir(name) 539 if err != nil { 540 ret.Code = -1 541 ret.Msg = err.Error() 542 ret.Data = map[string]interface{}{"closeTimeout": 5000} 543 return 544 } 545} 546 547func setSyncGenerateConflictDoc(c *gin.Context) { 548 ret := gulu.Ret.NewResult() 549 defer c.JSON(http.StatusOK, ret) 550 551 arg, ok := util.JsonArg(c, ret) 552 if !ok { 553 return 554 } 555 556 enabled := arg["enabled"].(bool) 557 model.SetSyncGenerateConflictDoc(enabled) 558} 559 560func setSyncEnable(c *gin.Context) { 561 ret := gulu.Ret.NewResult() 562 defer c.JSON(http.StatusOK, ret) 563 564 arg, ok := util.JsonArg(c, ret) 565 if !ok { 566 return 567 } 568 569 enabled := arg["enabled"].(bool) 570 model.SetSyncEnable(enabled) 571} 572 573func setSyncInterval(c *gin.Context) { 574 ret := gulu.Ret.NewResult() 575 defer c.JSON(http.StatusOK, ret) 576 arg, ok := util.JsonArg(c, ret) 577 if !ok { 578 return 579 } 580 interval := int(arg["interval"].(float64)) 581 model.SetSyncInterval(interval) 582} 583 584func setSyncPerception(c *gin.Context) { 585 ret := gulu.Ret.NewResult() 586 defer c.JSON(http.StatusOK, ret) 587 588 arg, ok := util.JsonArg(c, ret) 589 if !ok { 590 return 591 } 592 593 enabled := arg["enabled"].(bool) 594 model.SetSyncPerception(enabled) 595} 596 597func setSyncMode(c *gin.Context) { 598 ret := gulu.Ret.NewResult() 599 defer c.JSON(http.StatusOK, ret) 600 601 arg, ok := util.JsonArg(c, ret) 602 if !ok { 603 return 604 } 605 606 mode := int(arg["mode"].(float64)) 607 model.SetSyncMode(mode) 608} 609 610func setSyncProvider(c *gin.Context) { 611 ret := gulu.Ret.NewResult() 612 defer c.JSON(http.StatusOK, ret) 613 614 arg, ok := util.JsonArg(c, ret) 615 if !ok { 616 return 617 } 618 619 provider := int(arg["provider"].(float64)) 620 err := model.SetSyncProvider(provider) 621 if err != nil { 622 ret.Code = -1 623 ret.Msg = err.Error() 624 ret.Data = map[string]interface{}{"closeTimeout": 5000} 625 return 626 } 627} 628 629func setSyncProviderS3(c *gin.Context) { 630 ret := gulu.Ret.NewResult() 631 defer c.JSON(http.StatusOK, ret) 632 633 arg, ok := util.JsonArg(c, ret) 634 if !ok { 635 return 636 } 637 638 s3Arg := arg["s3"].(interface{}) 639 data, err := gulu.JSON.MarshalJSON(s3Arg) 640 if err != nil { 641 ret.Code = -1 642 ret.Msg = err.Error() 643 ret.Data = map[string]interface{}{"closeTimeout": 5000} 644 return 645 } 646 647 s3 := &conf.S3{} 648 if err = gulu.JSON.UnmarshalJSON(data, s3); err != nil { 649 ret.Code = -1 650 ret.Msg = err.Error() 651 ret.Data = map[string]interface{}{"closeTimeout": 5000} 652 return 653 } 654 655 err = model.SetSyncProviderS3(s3) 656 if err != nil { 657 ret.Code = -1 658 ret.Msg = err.Error() 659 ret.Data = map[string]interface{}{"closeTimeout": 5000} 660 return 661 } 662} 663 664func setSyncProviderWebDAV(c *gin.Context) { 665 ret := gulu.Ret.NewResult() 666 defer c.JSON(http.StatusOK, ret) 667 668 arg, ok := util.JsonArg(c, ret) 669 if !ok { 670 return 671 } 672 673 webdavArg := arg["webdav"].(interface{}) 674 data, err := gulu.JSON.MarshalJSON(webdavArg) 675 if err != nil { 676 ret.Code = -1 677 ret.Msg = err.Error() 678 ret.Data = map[string]interface{}{"closeTimeout": 5000} 679 return 680 } 681 682 webdav := &conf.WebDAV{} 683 if err = gulu.JSON.UnmarshalJSON(data, webdav); err != nil { 684 ret.Code = -1 685 ret.Msg = err.Error() 686 ret.Data = map[string]interface{}{"closeTimeout": 5000} 687 return 688 } 689 690 err = model.SetSyncProviderWebDAV(webdav) 691 if err != nil { 692 ret.Code = -1 693 ret.Msg = err.Error() 694 ret.Data = map[string]interface{}{"closeTimeout": 5000} 695 return 696 } 697} 698 699func setSyncProviderLocal(c *gin.Context) { 700 ret := gulu.Ret.NewResult() 701 defer c.JSON(http.StatusOK, ret) 702 703 arg, ok := util.JsonArg(c, ret) 704 if !ok { 705 return 706 } 707 708 localArg := arg["local"].(interface{}) 709 data, err := gulu.JSON.MarshalJSON(localArg) 710 if err != nil { 711 ret.Code = -1 712 ret.Msg = err.Error() 713 ret.Data = map[string]interface{}{"closeTimeout": 5000} 714 return 715 } 716 717 local := &conf.Local{} 718 if err = gulu.JSON.UnmarshalJSON(data, local); err != nil { 719 ret.Code = -1 720 ret.Msg = err.Error() 721 ret.Data = map[string]interface{}{"closeTimeout": 5000} 722 return 723 } 724 725 err = model.SetSyncProviderLocal(local) 726 if err != nil { 727 ret.Code = -1 728 ret.Msg = err.Error() 729 ret.Data = map[string]interface{}{"closeTimeout": 5000} 730 return 731 } 732 733 ret.Data = map[string]interface{}{ 734 "local": local, 735 } 736} 737 738func setCloudSyncDir(c *gin.Context) { 739 ret := gulu.Ret.NewResult() 740 defer c.JSON(http.StatusOK, ret) 741 742 arg, ok := util.JsonArg(c, ret) 743 if !ok { 744 return 745 } 746 747 name := arg["name"].(string) 748 model.SetCloudSyncDir(name) 749}