A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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}