A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at upstream/main 799 lines 19 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 "io" 21 "net/http" 22 "os" 23 "path/filepath" 24 "strings" 25 "time" 26 27 "github.com/88250/gulu" 28 "github.com/88250/lute" 29 "github.com/88250/lute/html" 30 "github.com/gin-gonic/gin" 31 "github.com/siyuan-note/filelock" 32 "github.com/siyuan-note/logging" 33 "github.com/siyuan-note/siyuan/kernel/conf" 34 "github.com/siyuan-note/siyuan/kernel/model" 35 "github.com/siyuan-note/siyuan/kernel/util" 36) 37 38func vacuumDataIndex(c *gin.Context) { 39 ret := gulu.Ret.NewResult() 40 defer c.JSON(http.StatusOK, ret) 41 42 model.VacuumDataIndex() 43} 44 45func rebuildDataIndex(c *gin.Context) { 46 ret := gulu.Ret.NewResult() 47 defer c.JSON(http.StatusOK, ret) 48 49 model.FullReindex() 50} 51 52func addMicrosoftDefenderExclusion(c *gin.Context) { 53 ret := gulu.Ret.NewResult() 54 defer c.JSON(http.StatusOK, ret) 55 56 if !gulu.OS.IsWindows() { 57 return 58 } 59 60 err := model.AddMicrosoftDefenderExclusion() 61 if nil != err { 62 ret.Code = -1 63 ret.Msg = err.Error() 64 } 65} 66 67func ignoreAddMicrosoftDefenderExclusion(c *gin.Context) { 68 ret := gulu.Ret.NewResult() 69 defer c.JSON(http.StatusOK, ret) 70 71 if !gulu.OS.IsWindows() { 72 return 73 } 74 75 model.Conf.System.MicrosoftDefenderExcluded = true 76 model.Conf.Save() 77} 78 79func getWorkspaceInfo(c *gin.Context) { 80 ret := gulu.Ret.NewResult() 81 defer c.JSON(http.StatusOK, ret) 82 83 ret.Data = map[string]any{ 84 "workspaceDir": util.WorkspaceDir, 85 "siyuanVer": util.Ver, 86 } 87} 88 89func getNetwork(c *gin.Context) { 90 ret := gulu.Ret.NewResult() 91 defer c.JSON(http.StatusOK, ret) 92 93 maskedConf, err := model.GetMaskedConf() 94 if err != nil { 95 ret.Code = -1 96 ret.Msg = "get conf failed: " + err.Error() 97 return 98 } 99 100 ret.Data = map[string]interface{}{ 101 "proxy": maskedConf.System.NetworkProxy, 102 } 103} 104 105func getChangelog(c *gin.Context) { 106 ret := gulu.Ret.NewResult() 107 defer c.JSON(http.StatusOK, ret) 108 109 data := map[string]interface{}{"show": false, "html": ""} 110 ret.Data = data 111 112 changelogsDir := filepath.Join(util.WorkingDir, "changelogs") 113 if !gulu.File.IsDir(changelogsDir) { 114 return 115 } 116 117 if !model.Conf.ShowChangelog { 118 return 119 } 120 121 changelogPath := filepath.Join(changelogsDir, "v"+util.Ver, "v"+util.Ver+"_"+model.Conf.Lang+".md") 122 if !gulu.File.IsExist(changelogPath) { 123 changelogPath = filepath.Join(changelogsDir, "v"+util.Ver, "v"+util.Ver+".md") 124 if !gulu.File.IsExist(changelogPath) { 125 logging.LogErrorf("changelog not found: %s", changelogPath) 126 return 127 } 128 } 129 130 contentData, err := os.ReadFile(changelogPath) 131 if err != nil { 132 logging.LogErrorf("read changelog failed: %s", err) 133 return 134 } 135 136 model.Conf.ShowChangelog = false 137 luteEngine := lute.New() 138 htmlContent := luteEngine.MarkdownStr("", string(contentData)) 139 htmlContent = util.LinkTarget(htmlContent, "") 140 141 data["show"] = true 142 data["html"] = htmlContent 143 ret.Data = data 144} 145 146func getEmojiConf(c *gin.Context) { 147 ret := gulu.Ret.NewResult() 148 defer c.JSON(http.StatusOK, ret) 149 150 builtConfPath := filepath.Join(util.AppearancePath, "emojis", "conf.json") 151 data, err := os.ReadFile(builtConfPath) 152 if err != nil { 153 logging.LogErrorf("read emojis conf.json failed: %s", err) 154 ret.Code = -1 155 ret.Msg = err.Error() 156 return 157 } 158 159 var conf []map[string]interface{} 160 if err = gulu.JSON.UnmarshalJSON(data, &conf); err != nil { 161 logging.LogErrorf("unmarshal emojis conf.json failed: %s", err) 162 ret.Code = -1 163 ret.Msg = err.Error() 164 return 165 } 166 167 customConfDir := filepath.Join(util.DataDir, "emojis") 168 custom := map[string]interface{}{ 169 "id": "custom", 170 "title": "Custom", 171 "title_zh_cn": "自定义", 172 "title_ja_jp": "カスタム", 173 } 174 items := []map[string]interface{}{} 175 custom["items"] = items 176 if gulu.File.IsDir(customConfDir) { 177 model.ClearCustomEmojis() 178 customEmojis, err := os.ReadDir(customConfDir) 179 if err != nil { 180 logging.LogErrorf("read custom emojis failed: %s", err) 181 } else { 182 for _, customEmoji := range customEmojis { 183 name := customEmoji.Name() 184 if strings.HasPrefix(name, ".") { 185 continue 186 } 187 188 if !util.IsValidUploadFileName(html.UnescapeString(name)) { 189 emojiFullName := filepath.Join(customConfDir, name) 190 name = util.FilterUploadEmojiFileName(name) 191 fullPathFilteredName := filepath.Join(customConfDir, name) 192 // XSS through emoji name https://github.com/siyuan-note/siyuan/issues/15034 193 logging.LogWarnf("renaming invalid custom emoji file [%s] to [%s]", name, fullPathFilteredName) 194 if removeErr := filelock.Rename(emojiFullName, fullPathFilteredName); nil != removeErr { 195 logging.LogErrorf("renaming invalid custom emoji file to [%s] failed: %s", fullPathFilteredName, removeErr) 196 } 197 } 198 199 if customEmoji.IsDir() { 200 // 子级 201 subCustomEmojis, err := os.ReadDir(filepath.Join(customConfDir, name)) 202 if err != nil { 203 logging.LogErrorf("read custom emojis failed: %s", err) 204 continue 205 } 206 207 for _, subCustomEmoji := range subCustomEmojis { 208 if subCustomEmoji.IsDir() { 209 continue 210 } 211 212 subName := subCustomEmoji.Name() 213 if strings.HasPrefix(subName, ".") { 214 continue 215 } 216 217 if !util.IsValidUploadFileName(html.UnescapeString(subName)) { 218 emojiFullName := filepath.Join(customConfDir, name, subName) 219 fullPathFilteredName := filepath.Join(customConfDir, name, util.FilterUploadEmojiFileName(subName)) 220 // XSS through emoji name https://github.com/siyuan-note/siyuan/issues/15034 221 logging.LogWarnf("renaming invalid custom emoji file [%s] to [%s]", subName, fullPathFilteredName) 222 if removeErr := filelock.Rename(emojiFullName, fullPathFilteredName); nil != removeErr { 223 logging.LogErrorf("renaming invalid custom emoji file to [%s] failed: %s", fullPathFilteredName, removeErr) 224 } 225 } 226 227 addCustomEmoji(name+"/"+subName, &items) 228 } 229 continue 230 } 231 232 addCustomEmoji(name, &items) 233 } 234 } 235 } 236 custom["items"] = items 237 conf = append([]map[string]interface{}{custom}, conf...) 238 239 ret.Data = conf 240 return 241} 242 243func addCustomEmoji(name string, items *[]map[string]interface{}) { 244 ext := filepath.Ext(name) 245 nameWithoutExt := strings.TrimSuffix(name, ext) 246 emoji := map[string]interface{}{ 247 "unicode": name, 248 "description": nameWithoutExt, 249 "description_zh_cn": nameWithoutExt, 250 "description_ja_jp": nameWithoutExt, 251 "keywords": nameWithoutExt, 252 } 253 *items = append(*items, emoji) 254 255 imgSrc := "/emojis/" + name 256 model.AddCustomEmoji(nameWithoutExt, imgSrc) 257} 258 259func checkUpdate(c *gin.Context) { 260 ret := gulu.Ret.NewResult() 261 defer c.JSON(http.StatusOK, ret) 262 263 arg, ok := util.JsonArg(c, ret) 264 if !ok { 265 return 266 } 267 268 showMsg := arg["showMsg"].(bool) 269 model.CheckUpdate(showMsg) 270} 271 272func exportLog(c *gin.Context) { 273 ret := gulu.Ret.NewResult() 274 defer c.JSON(http.StatusOK, ret) 275 276 zipPath := model.ExportSystemLog() 277 ret.Data = map[string]interface{}{ 278 "zip": zipPath, 279 } 280} 281 282func exportConf(c *gin.Context) { 283 ret := gulu.Ret.NewResult() 284 defer c.JSON(http.StatusOK, ret) 285 286 logging.LogInfof("exporting conf...") 287 288 name := "siyuan-conf-" + time.Now().Format("20060102150405") + ".json" 289 tmpDir := filepath.Join(util.TempDir, "export") 290 if err := os.MkdirAll(tmpDir, 0755); err != nil { 291 logging.LogErrorf("export conf failed: %s", err) 292 ret.Code = -1 293 ret.Msg = err.Error() 294 return 295 } 296 297 data, err := gulu.JSON.MarshalJSON(model.Conf) 298 if err != nil { 299 logging.LogErrorf("export conf failed: %s", err) 300 ret.Code = -1 301 ret.Msg = err.Error() 302 return 303 } 304 clonedConf := &model.AppConf{} 305 if err = gulu.JSON.UnmarshalJSON(data, clonedConf); err != nil { 306 logging.LogErrorf("export conf failed: %s", err) 307 ret.Code = -1 308 ret.Msg = err.Error() 309 return 310 } 311 312 if nil != clonedConf.Appearance { 313 clonedConf.Appearance.DarkThemes = nil 314 clonedConf.Appearance.LightThemes = nil 315 clonedConf.Appearance.Icons = nil 316 } 317 if nil != clonedConf.Editor { 318 clonedConf.Editor.Emoji = []string{} 319 } 320 if nil != clonedConf.Export { 321 clonedConf.Export.PandocBin = "" 322 } 323 clonedConf.UserData = "" 324 clonedConf.Account = nil 325 clonedConf.AccessAuthCode = "" 326 if nil != clonedConf.System { 327 clonedConf.System.ID = "" 328 clonedConf.System.Name = "" 329 clonedConf.System.OSPlatform = "" 330 clonedConf.System.Container = "" 331 clonedConf.System.IsMicrosoftStore = false 332 clonedConf.System.IsInsider = false 333 clonedConf.System.MicrosoftDefenderExcluded = false 334 } 335 clonedConf.Sync = nil 336 clonedConf.Stat = nil 337 clonedConf.Api = nil 338 clonedConf.Repo = nil 339 clonedConf.Publish = nil 340 clonedConf.CloudRegion = 0 341 clonedConf.DataIndexState = 0 342 343 data, err = gulu.JSON.MarshalIndentJSON(clonedConf, "", " ") 344 if err != nil { 345 logging.LogErrorf("export conf failed: %s", err) 346 ret.Code = -1 347 ret.Msg = err.Error() 348 return 349 } 350 351 tmp := filepath.Join(tmpDir, name) 352 if err = os.WriteFile(tmp, data, 0644); err != nil { 353 logging.LogErrorf("export conf failed: %s", err) 354 ret.Code = -1 355 ret.Msg = err.Error() 356 return 357 } 358 359 zipFile, err := gulu.Zip.Create(tmp + ".zip") 360 if err != nil { 361 logging.LogErrorf("export conf failed: %s", err) 362 ret.Code = -1 363 ret.Msg = err.Error() 364 return 365 } 366 367 if err = zipFile.AddEntry(name, tmp); err != nil { 368 logging.LogErrorf("export conf failed: %s", err) 369 ret.Code = -1 370 ret.Msg = err.Error() 371 return 372 } 373 374 if err = zipFile.Close(); err != nil { 375 logging.LogErrorf("export conf failed: %s", err) 376 ret.Code = -1 377 ret.Msg = err.Error() 378 return 379 } 380 381 logging.LogInfof("exported conf") 382 383 zipPath := "/export/" + name + ".zip" 384 ret.Data = map[string]interface{}{ 385 "name": name, 386 "zip": zipPath, 387 } 388} 389 390func importConf(c *gin.Context) { 391 ret := gulu.Ret.NewResult() 392 defer c.JSON(200, ret) 393 394 logging.LogInfof("importing conf...") 395 396 form, err := c.MultipartForm() 397 if err != nil { 398 logging.LogErrorf("read upload file failed: %s", err) 399 ret.Code = -1 400 ret.Msg = err.Error() 401 return 402 } 403 404 files := form.File["file"] 405 if 1 != len(files) { 406 ret.Code = -1 407 ret.Msg = "invalid upload file" 408 return 409 } 410 411 f := files[0] 412 fh, err := f.Open() 413 if err != nil { 414 logging.LogErrorf("read upload file failed: %s", err) 415 ret.Code = -1 416 ret.Msg = err.Error() 417 return 418 } 419 420 data, err := io.ReadAll(fh) 421 fh.Close() 422 if err != nil { 423 logging.LogErrorf("read upload file failed: %s", err) 424 ret.Code = -1 425 ret.Msg = err.Error() 426 return 427 } 428 429 importDir := filepath.Join(util.TempDir, "import") 430 if err = os.MkdirAll(importDir, 0755); err != nil { 431 logging.LogErrorf("import conf failed: %s", err) 432 ret.Code = -1 433 ret.Msg = err.Error() 434 return 435 } 436 437 tmp := filepath.Join(importDir, f.Filename) 438 if err = os.WriteFile(tmp, data, 0644); err != nil { 439 logging.LogErrorf("import conf failed: %s", err) 440 ret.Code = -1 441 ret.Msg = err.Error() 442 return 443 } 444 445 tmpDir := filepath.Join(importDir, "conf") 446 os.RemoveAll(tmpDir) 447 if strings.HasSuffix(strings.ToLower(tmp), ".zip") { 448 if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil { 449 logging.LogErrorf("import conf failed: %s", err) 450 ret.Code = -1 451 ret.Msg = err.Error() 452 return 453 } 454 } else if strings.HasSuffix(strings.ToLower(tmp), ".json") { 455 if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil { 456 logging.LogErrorf("import conf failed: %s", err) 457 ret.Code = -1 458 ret.Msg = err.Error() 459 } 460 } else { 461 logging.LogErrorf("invalid conf package") 462 ret.Code = -1 463 ret.Msg = "invalid conf package" 464 return 465 } 466 467 entries, err := os.ReadDir(tmpDir) 468 if err != nil { 469 logging.LogErrorf("import conf failed: %s", err) 470 ret.Code = -1 471 ret.Msg = err.Error() 472 return 473 } 474 475 if 1 != len(entries) { 476 logging.LogErrorf("invalid conf package") 477 ret.Code = -1 478 ret.Msg = "invalid conf package" 479 return 480 } 481 482 tmp = filepath.Join(tmpDir, entries[0].Name()) 483 data, err = os.ReadFile(tmp) 484 if err != nil { 485 logging.LogErrorf("import conf failed: %s", err) 486 ret.Code = -1 487 ret.Msg = err.Error() 488 return 489 } 490 491 importedConf := model.NewAppConf() 492 if err = gulu.JSON.UnmarshalJSON(data, importedConf); err != nil { 493 logging.LogErrorf("import conf failed: %s", err) 494 ret.Code = -1 495 ret.Msg = err.Error() 496 return 497 } 498 499 model.Conf.FileTree = importedConf.FileTree 500 model.Conf.Tag = importedConf.Tag 501 model.Conf.Editor = importedConf.Editor 502 model.Conf.Export = importedConf.Export 503 model.Conf.Graph = importedConf.Graph 504 model.Conf.UILayout = importedConf.UILayout 505 model.Conf.System = importedConf.System 506 model.Conf.Keymap = importedConf.Keymap 507 model.Conf.Search = importedConf.Search 508 model.Conf.Flashcard = importedConf.Flashcard 509 model.Conf.AI = importedConf.AI 510 model.Conf.Bazaar = importedConf.Bazaar 511 model.Conf.Save() 512 513 logging.LogInfof("imported conf") 514} 515 516func getConf(c *gin.Context) { 517 ret := gulu.Ret.NewResult() 518 defer c.JSON(http.StatusOK, ret) 519 520 maskedConf, err := model.GetMaskedConf() 521 if err != nil { 522 ret.Code = -1 523 ret.Msg = "get conf failed: " + err.Error() 524 return 525 } 526 527 if !maskedConf.Sync.Enabled || (0 == maskedConf.Sync.Provider && !model.IsSubscriber()) { 528 maskedConf.Sync.Stat = model.Conf.Language(53) 529 } 530 531 // REF: https://github.com/siyuan-note/siyuan/issues/11364 532 role := model.GetGinContextRole(c) 533 isPublish := model.IsReadOnlyRole(role) 534 if isPublish { 535 maskedConf.ReadOnly = true 536 } 537 if !model.IsValidRole(role, []model.Role{ 538 model.RoleAdministrator, 539 }) { 540 model.HideConfSecret(maskedConf) 541 } 542 543 ret.Data = map[string]interface{}{ 544 "conf": maskedConf, 545 "start": !util.IsUILoaded, 546 "isPublish": isPublish, 547 } 548} 549 550func setUILayout(c *gin.Context) { 551 ret := gulu.Ret.NewResult() 552 defer c.JSON(http.StatusOK, ret) 553 554 if util.ReadOnly { 555 return 556 } 557 558 arg, ok := util.JsonArg(c, ret) 559 if !ok { 560 return 561 } 562 563 param, err := gulu.JSON.MarshalJSON(arg["layout"]) 564 if err != nil { 565 ret.Code = -1 566 ret.Msg = err.Error() 567 return 568 } 569 570 uiLayout := &conf.UILayout{} 571 if err = gulu.JSON.UnmarshalJSON(param, uiLayout); err != nil { 572 ret.Code = -1 573 ret.Msg = err.Error() 574 return 575 } 576 577 model.Conf.SetUILayout(uiLayout) 578 model.Conf.Save() 579} 580 581func setAPIToken(c *gin.Context) { 582 ret := gulu.Ret.NewResult() 583 defer c.JSON(http.StatusOK, ret) 584 585 arg, ok := util.JsonArg(c, ret) 586 if !ok { 587 return 588 } 589 590 token := arg["token"].(string) 591 model.Conf.Api.Token = token 592 model.Conf.Save() 593} 594 595func setAccessAuthCode(c *gin.Context) { 596 ret := gulu.Ret.NewResult() 597 defer c.JSON(http.StatusOK, ret) 598 599 arg, ok := util.JsonArg(c, ret) 600 if !ok { 601 return 602 } 603 604 aac := arg["accessAuthCode"].(string) 605 if model.MaskedAccessAuthCode == aac { 606 aac = model.Conf.AccessAuthCode 607 } 608 609 aac = strings.TrimSpace(aac) 610 aac = util.RemoveInvalid(aac) 611 612 model.Conf.AccessAuthCode = aac 613 model.Conf.Save() 614 615 session := util.GetSession(c) 616 workspaceSession := util.GetWorkspaceSession(session) 617 workspaceSession.AccessAuthCode = aac 618 session.Save(c) 619 go func() { 620 time.Sleep(200 * time.Millisecond) 621 util.ReloadUI() 622 }() 623 return 624} 625 626func setFollowSystemLockScreen(c *gin.Context) { 627 ret := gulu.Ret.NewResult() 628 defer c.JSON(http.StatusOK, ret) 629 630 arg, ok := util.JsonArg(c, ret) 631 if !ok { 632 return 633 } 634 635 lockScreenMode := int(arg["lockScreenMode"].(float64)) 636 637 model.Conf.System.LockScreenMode = lockScreenMode 638 model.Conf.Save() 639 return 640} 641 642func getSysFonts(c *gin.Context) { 643 ret := gulu.Ret.NewResult() 644 defer c.JSON(http.StatusOK, ret) 645 ret.Data = util.LoadSysFonts() 646} 647 648func version(c *gin.Context) { 649 ret := gulu.Ret.NewResult() 650 defer c.JSON(http.StatusOK, ret) 651 652 ret.Data = util.Ver 653} 654 655func currentTime(c *gin.Context) { 656 ret := gulu.Ret.NewResult() 657 defer c.JSON(http.StatusOK, ret) 658 659 ret.Data = util.CurrentTimeMillis() 660} 661 662func bootProgress(c *gin.Context) { 663 ret := gulu.Ret.NewResult() 664 defer c.JSON(http.StatusOK, ret) 665 666 progress, details := util.GetBootProgressDetails() 667 ret.Data = map[string]interface{}{"progress": progress, "details": details} 668} 669 670func setAppearanceMode(c *gin.Context) { 671 ret := gulu.Ret.NewResult() 672 defer c.JSON(http.StatusOK, ret) 673 674 arg, ok := util.JsonArg(c, ret) 675 if !ok { 676 return 677 } 678 679 mode := int(arg["mode"].(float64)) 680 model.Conf.Appearance.Mode = mode 681 if 0 == mode { 682 model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeLight, "theme.js")) 683 } else { 684 model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeDark, "theme.js")) 685 } 686 model.Conf.Save() 687 688 ret.Data = map[string]interface{}{ 689 "appearance": model.Conf.Appearance, 690 } 691} 692 693func setNetworkServe(c *gin.Context) { 694 ret := gulu.Ret.NewResult() 695 defer c.JSON(http.StatusOK, ret) 696 697 arg, ok := util.JsonArg(c, ret) 698 if !ok { 699 return 700 } 701 702 networkServe := arg["networkServe"].(bool) 703 model.Conf.System.NetworkServe = networkServe 704 model.Conf.Save() 705 706 util.PushMsg(model.Conf.Language(42), 1000*15) 707 time.Sleep(time.Second * 3) 708} 709 710func setAutoLaunch(c *gin.Context) { 711 ret := gulu.Ret.NewResult() 712 defer c.JSON(http.StatusOK, ret) 713 714 arg, ok := util.JsonArg(c, ret) 715 if !ok { 716 return 717 } 718 719 autoLaunch := int(arg["autoLaunch"].(float64)) 720 model.Conf.System.AutoLaunch2 = autoLaunch 721 model.Conf.Save() 722} 723 724func setDownloadInstallPkg(c *gin.Context) { 725 ret := gulu.Ret.NewResult() 726 defer c.JSON(http.StatusOK, ret) 727 728 arg, ok := util.JsonArg(c, ret) 729 if !ok { 730 return 731 } 732 733 downloadInstallPkg := arg["downloadInstallPkg"].(bool) 734 model.Conf.System.DownloadInstallPkg = downloadInstallPkg 735 model.Conf.Save() 736} 737 738func setNetworkProxy(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 scheme := arg["scheme"].(string) 748 host := arg["host"].(string) 749 port := arg["port"].(string) 750 model.Conf.System.NetworkProxy = &conf.NetworkProxy{ 751 Scheme: scheme, 752 Host: host, 753 Port: port, 754 } 755 model.Conf.Save() 756 757 proxyURL := model.Conf.System.NetworkProxy.String() 758 util.SetNetworkProxy(proxyURL) 759 util.PushMsg(model.Conf.Language(102), 3000) 760} 761 762func addUIProcess(c *gin.Context) { 763 pid := c.Query("pid") 764 util.UIProcessIDs.Store(pid, true) 765} 766 767func exit(c *gin.Context) { 768 ret := gulu.Ret.NewResult() 769 defer c.JSON(http.StatusOK, ret) 770 771 arg, ok := util.JsonArg(c, ret) 772 if !ok { 773 return 774 } 775 776 forceArg := arg["force"] 777 var force bool 778 if nil != forceArg { 779 force = forceArg.(bool) 780 } 781 782 execInstallPkgArg := arg["execInstallPkg"] // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装 783 execInstallPkg := 0 784 if nil != execInstallPkgArg { 785 execInstallPkg = int(execInstallPkgArg.(float64)) 786 } 787 788 exitCode := model.Close(force, true, execInstallPkg) 789 ret.Code = exitCode 790 switch exitCode { 791 case 0: 792 case 1: // 同步执行失败 793 ret.Msg = model.Conf.Language(96) + "<div class=\"fn__space\"></div><button class=\"b3-button b3-button--white\">" + model.Conf.Language(97) + "</button>" 794 ret.Data = map[string]interface{}{"closeTimeout": 0} 795 case 2: // 提示新安装包 796 ret.Msg = model.Conf.Language(61) 797 ret.Data = map[string]interface{}{"closeTimeout": 0} 798 } 799}