This commit is contained in:
Liang Ding 2022-05-26 15:18:53 +08:00
parent e650b8100c
commit f40ed985e1
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
1214 changed files with 345766 additions and 9 deletions

24
kernel/util/cmdattr.go Normal file
View file

@ -0,0 +1,24 @@
// SiYuan - Build Your Eternal Digital Garden
// 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/>.
//go:build !windows
package util
import "os/exec"
func CmdAttr(cmd *exec.Cmd) {
}

View file

@ -0,0 +1,26 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"os/exec"
"syscall"
)
func CmdAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
}

76
kernel/util/crypt.go Normal file
View file

@ -0,0 +1,76 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
)
var SK = []byte("696D897C9AA0611B")
func AESEncrypt(str string) string {
buf := &bytes.Buffer{}
buf.Grow(4096)
_, err := hex.NewEncoder(buf).Write([]byte(str))
if nil != err {
LogErrorf("encrypt failed: %s", err)
return ""
}
data := buf.Bytes()
block, err := aes.NewCipher(SK)
if nil != err {
LogErrorf("encrypt failed: %s", err)
return ""
}
cbc := cipher.NewCBCEncrypter(block, []byte("RandomInitVector"))
content := data
content = pkcs5Padding(content, block.BlockSize())
crypted := make([]byte, len(content))
cbc.CryptBlocks(crypted, content)
return hex.EncodeToString(crypted)
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func AESDecrypt(cryptStr string) []byte {
crypt, err := hex.DecodeString(cryptStr)
if nil != err {
LogErrorf("decrypt failed: %s", err)
return nil
}
block, err := aes.NewCipher(SK)
if nil != err {
return nil
}
cbc := cipher.NewCBCDecrypter(block, []byte("RandomInitVector"))
decrypted := make([]byte, len(crypt))
cbc.CryptBlocks(decrypted, crypt)
return pkcs5Trimming(decrypted)
}
func pkcs5Trimming(encrypt []byte) []byte {
padding := encrypt[len(encrypt)-1]
return encrypt[:len(encrypt)-int(padding)]
}

42
kernel/util/emoji.go Normal file
View file

@ -0,0 +1,42 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import "regexp"
var emojiRegex = regexp.MustCompile(`/([0-9#][\x{20E3}])|` +
`[\x{00ae}\x{00a9}\x{203C}\x{2047}\x{2048}\x{2049}\x{3030}\x{303D}\x{2139}\x{2122}\x{3297}\x{3299}]|` +
`[\x{2190}-\x{21FF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{2300}-\x{23FF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{2460}-\x{24FF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{25A0}-\x{25FF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{2600}-\x{27BF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{2900}-\x{297F}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{2B00}-\x{2BF0}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{1F000}-\x{1F6FF}]|[\x{FE00}-\x{FEFF}]|` +
`[\x{1F600}-\x{1F64F}]|` +
`[\x{1F680}-\x{1F6FF}]|` +
`[\x{1F900}-\x{1F9FF}]|` +
`[\x{1F1E0}-\x{1F1FF}]|` +
`[\x{1D100}-\x{1D1FF}]|` +
`[\x{2600}-\x{26FF}]|` +
`[\x{2700}-\x{27BF}]|` +
`[\x{10000}-\x{E01EF}]`)
func RemoveEmoji(text string) string {
return emojiRegex.ReplaceAllString(text, "")
}

218
kernel/util/file.go Normal file
View file

@ -0,0 +1,218 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/88250/gulu"
)
func IsEmptyDir(p string) bool {
if !gulu.File.IsDir(p) {
return false
}
files, err := os.ReadDir(p)
if nil != err {
return false
}
return 1 > len(files)
}
func IsValidJSON(p string) bool {
if !gulu.File.IsExist(p) {
return false
}
data, err := os.ReadFile(p)
if nil != err {
LogErrorf("read json file [%s] failed: %s", p, err)
return false
}
json := map[string]interface{}{}
if err = gulu.JSON.UnmarshalJSON(data, &json); nil != err {
LogErrorf("parse json file [%s] failed: %s", p, err)
return false
}
return true
}
func RemoveID(name string) string {
ext := path.Ext(name)
name = strings.TrimSuffix(name, ext)
if 23 < len(name) {
name = name[:len(name)-23]
}
return name + ext
}
func LastID(p string) (name, id string) {
name = path.Base(p)
ext := path.Ext(name)
id = strings.TrimSuffix(name, ext)
if 22 < len(id) {
id = id[len(id)-22:]
}
return
}
func LatestTmpFile(p string) string {
dir, base := filepath.Split(p)
files, err := os.ReadDir(dir)
if nil != err {
LogErrorf("read dir [%s] failed: %s", dir, err)
return ""
}
var tmps []os.DirEntry
for _, f := range files {
if f.IsDir() {
continue
}
if strings.HasSuffix(f.Name(), ".tmp") && strings.HasPrefix(f.Name(), base) && len(base)+7+len(".tmp") == len(f.Name()) {
tmps = append(tmps, f)
}
}
if 1 > len(tmps) {
return ""
}
sort.Slice(tmps, func(i, j int) bool {
info1, err := tmps[i].Info()
if nil != err {
LogErrorf("read file info [%s] failed: %s", tmps[i].Name(), err)
return false
}
info2, err := tmps[j].Info()
if nil != err {
LogErrorf("read file info [%s] failed: %s", tmps[j].Name(), err)
return false
}
return info1.ModTime().After(info2.ModTime())
})
return filepath.Join(dir, tmps[0].Name())
}
func IsCorruptedSYData(data []byte) bool {
if 64 > len(data) || '{' != data[0] {
return true
}
return false
}
func FilterUploadFileName(name string) string {
ret := FilterFileName(name)
ret = strings.ReplaceAll(ret, "~", "")
//ret = strings.ReplaceAll(ret, "_", "") // https://github.com/siyuan-note/siyuan/issues/3534
ret = strings.ReplaceAll(ret, "[", "")
ret = strings.ReplaceAll(ret, "]", "")
ret = strings.ReplaceAll(ret, "(", "")
ret = strings.ReplaceAll(ret, ")", "")
ret = strings.ReplaceAll(ret, "!", "")
ret = strings.ReplaceAll(ret, "`", "")
ret = strings.ReplaceAll(ret, "&", "")
ret = strings.ReplaceAll(ret, "{", "")
ret = strings.ReplaceAll(ret, "}", "")
ret = strings.ReplaceAll(ret, "=", "")
ret = strings.ReplaceAll(ret, "#", "")
ret = strings.ReplaceAll(ret, "%", "")
ret = strings.ReplaceAll(ret, "$", "")
return ret
}
func FilterFilePath(p string) (ret string) {
ret = strings.ReplaceAll(p, "/", "__@sep__")
ret = FilterFileName(ret)
ret = strings.ReplaceAll(ret, "__@sep__", "/")
return
}
func FilterFileName(name string) string {
name = strings.ReplaceAll(name, "\\", "")
name = strings.ReplaceAll(name, "/", "")
name = strings.ReplaceAll(name, ":", "")
name = strings.ReplaceAll(name, "*", "")
name = strings.ReplaceAll(name, "?", "")
name = strings.ReplaceAll(name, "\"", "")
name = strings.ReplaceAll(name, "'", "")
name = strings.ReplaceAll(name, "<", "")
name = strings.ReplaceAll(name, ">", "")
name = strings.ReplaceAll(name, "|", "")
return name
}
func IsSubFolder(parent, sub string) bool {
if 1 > len(parent) || 1 > len(sub) {
return false
}
if gulu.OS.IsWindows() {
if filepath.IsAbs(parent) && filepath.IsAbs(sub) {
if strings.ToLower(parent)[0] != strings.ToLower(sub)[0] {
// 不在一个盘
return false
}
}
}
up := ".." + string(os.PathSeparator)
rel, err := filepath.Rel(parent, sub)
if err != nil {
return false
}
if !strings.HasPrefix(rel, up) && rel != ".." {
return true
}
return false
}
const CloudSingleFileMaxSizeLimit = 96 * 1000 * 1000
func SizeOfDirectory(path string, includeBigFile bool) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if nil != err {
return err
}
if !info.IsDir() {
s := info.Size()
if CloudSingleFileMaxSizeLimit < s {
if includeBigFile {
size += s
}
} else {
size += s
}
} else {
size += 4096
}
return nil
})
if nil != err {
LogErrorf("size of dir [%s] failed: %s", path, err)
}
return size, err
}
func IsReservedFilename(baseName string) bool {
return "assets" == baseName || "templates" == baseName || "widgets" == baseName || "emojis" == baseName || ".siyuan" == baseName || strings.HasPrefix(baseName, ".")
}

154
kernel/util/font.go Normal file
View file

@ -0,0 +1,154 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"os"
"sort"
"strings"
"github.com/ConradIrwin/font/sfnt"
"github.com/flopp/go-findfont"
ttc "golang.org/x/image/font/sfnt"
textUnicode "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
func GetSysFonts(currentLanguage string) (ret []string) {
fonts := loadFonts(currentLanguage)
ret = RemoveDuplicatedElem(fonts)
ret = removeUnusedFonts(ret)
sort.Strings(ret)
return
}
func removeUnusedFonts(fonts []string) (ret []string) {
ret = []string{}
for _, font := range fonts {
if strings.HasPrefix(font, "Noto Sans") {
continue
}
ret = append(ret, font)
}
return
}
func loadFonts(currentLanguage string) (ret []string) {
ret = []string{}
for _, f := range findfont.List() {
if strings.HasSuffix(strings.ToLower(f), ".ttc") {
data, err := os.ReadFile(f)
if nil != err {
LogErrorf("read font file [%s] failed: %s", f, err)
continue
}
collection, err := ttc.ParseCollection(data)
if nil != err {
//LogErrorf("parse font collection [%s] failed: %s", f, err)
continue
}
for i := 0; i < collection.NumFonts(); i++ {
font, err := collection.Font(i)
if nil != err {
//LogErrorf("get font [%s] failed: %s", f, err)
continue
}
if family := parseFontFamily(font); "" != family {
ret = append(ret, family)
//LogInfof("[%s] [%s]", f, family)
}
}
} else if strings.HasSuffix(strings.ToLower(f), ".otf") || strings.HasSuffix(strings.ToLower(f), ".ttf") {
fontFile, err := os.Open(f)
if nil != err {
//LogErrorf("open font file [%s] failed: %s", f, err)
continue
}
font, err := sfnt.Parse(fontFile)
if nil != err {
//LogErrorf("parse font [%s] failed: %s", f, err)
continue
}
t, err := font.NameTable()
if nil != err {
//LogErrorf("parse font name table [%s] failed: %s", f, err)
return
}
fontFile.Close()
var family, familyChinese string
for _, e := range t.List() {
if sfnt.NameFontFamily != e.NameID && sfnt.NamePreferredFamily != e.NameID {
continue
}
if sfnt.PlatformLanguageID(1033) == e.LanguageID {
v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value)
if nil != err {
//LogErrorf("decode font family [%s] failed: %s", f, err)
continue
}
val := string(v)
if sfnt.NameFontFamily == e.NameID && "" != val {
family = val
}
if sfnt.NamePreferredFamily == e.NameID && "" != val {
family = val
}
} else if sfnt.PlatformLanguageID(2052) == e.LanguageID {
if "zh_CN" != currentLanguage {
continue
}
v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value)
if nil != err {
//LogErrorf("decode font family [%s] failed: %s", f, err)
continue
}
val := string(v)
if sfnt.NameFontFamily == e.NameID && "" != val {
familyChinese = val
}
if sfnt.NamePreferredFamily == e.NameID && "" != val {
familyChinese = val
}
}
}
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
//LogInfof("[%s] [%s]", f, family)
}
if "" != familyChinese && !strings.HasPrefix(familyChinese, ".") {
ret = append(ret, familyChinese)
//LogInfof("[%s] [%s]", f, family)
}
}
}
return
}
func parseFontFamily(font *ttc.Font) string {
family, _ := font.Name(nil, ttc.NameIDTypographicFamily)
if "" == family {
family, _ = font.Name(nil, ttc.NameIDFamily)
}
if strings.HasPrefix(family, ".") {
return ""
}
return family
}

340
kernel/util/log.go Normal file
View file

@ -0,0 +1,340 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"bytes"
"fmt"
"io"
stdlog "log"
"os"
"runtime"
"runtime/debug"
"strings"
"github.com/88250/gulu"
"github.com/getsentry/sentry-go"
)
func ShortStack() string {
output := string(debug.Stack())
lines := strings.Split(output, "\n")
if 5 < len(lines) {
lines = lines[5:]
}
buf := bytes.Buffer{}
for _, l := range lines {
if strings.Contains(l, "gin-gonic") {
break
}
buf.WriteString(" ")
buf.WriteString(l)
buf.WriteByte('\n')
}
return buf.String()
}
var (
logger *Logger
logFile *os.File
)
func LogTracef(format string, v ...interface{}) {
if !logger.IsTraceEnabled() {
return
}
defer closeLogger()
openLogger()
logger.Tracef(format, v...)
}
func LogDebugf(format string, v ...interface{}) {
if !logger.IsDebugEnabled() {
return
}
defer closeLogger()
openLogger()
logger.Debugf(format, v...)
}
func LogInfof(format string, v ...interface{}) {
defer closeLogger()
openLogger()
logger.Infof(format, v...)
}
func LogErrorf(format string, v ...interface{}) {
defer closeLogger()
openLogger()
logger.Errorf(format, v...)
}
func LogWarnf(format string, v ...interface{}) {
if !logger.IsWarnEnabled() {
return
}
defer closeLogger()
openLogger()
logger.Warnf(format, v...)
}
func LogFatalf(format string, v ...interface{}) {
openLogger()
logger.Fatalf(format, v...)
}
func openLogger() {
if gulu.File.IsExist(LogPath) {
if size := gulu.File.GetFileSize(LogPath); 1024*1024*2 <= size {
// 日志文件大于 2M 的话删了重建
os.Remove(LogPath)
}
}
var err error
logFile, err = os.OpenFile(LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if nil != err {
stdlog.Fatalf("create log file [%s] failed: %s", LogPath, err)
}
logger = NewLogger(io.MultiWriter(os.Stdout, logFile))
}
func closeLogger() {
logFile.Close()
}
func Recover() {
if e := recover(); nil != e {
stack := stack()
msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, stack)
LogErrorf(msg)
}
}
var (
dunno = []byte("???")
centerDot = []byte("·")
dot = []byte(".")
slash = []byte("/")
)
// stack implements Stack, skipping 2 frames.
func stack() []byte {
buf := &bytes.Buffer{} // the returned data
// As we loop, we open files and read them. These variables record the currently
// loaded file.
var lines [][]byte
var lastFile string
for i := 2; ; i++ { // Caller we care about is the user, 2 frames up
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
// Print this much at least. If we can't find the source, it won't show.
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile {
data, err := os.ReadFile(file)
if err != nil {
continue
}
lines = bytes.Split(data, []byte{'\n'})
lastFile = file
}
line-- // in stack trace, lines are 1-indexed but our array is 0-indexed
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
}
return buf.Bytes()
}
// source returns a space-trimmed slice of the n'th line.
func source(lines [][]byte, n int) []byte {
if n < 0 || n >= len(lines) {
return dunno
}
return bytes.Trim(lines[n], " \t")
}
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) []byte {
fn := runtime.FuncForPC(pc)
if fn == nil {
return dunno
}
name := []byte(fn.Name())
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
name = name[lastslash+1:]
}
if period := bytes.Index(name, dot); period >= 0 {
name = name[period+1:]
}
name = bytes.Replace(name, centerDot, dot, -1)
return name
}
// Logging level.
const (
Off = iota
Trace
Debug
Info
Warn
Error
Fatal
)
// the global default logging level, it will be used for creating logger.
var logLevel = Debug
// Logger represents a simple logger with level.
// The underlying logger is the standard Go logging "log".
type Logger struct {
level int
logger *stdlog.Logger
}
// NewLogger creates a logger.
func NewLogger(out io.Writer) *Logger {
ret := &Logger{level: logLevel, logger: stdlog.New(out, "", stdlog.Ldate|stdlog.Ltime|stdlog.Lshortfile)}
return ret
}
// SetLogLevel sets the logging level of all loggers.
func SetLogLevel(level string) {
logLevel = getLevel(level)
}
// getLevel gets logging level int value corresponding to the specified level.
func getLevel(level string) int {
level = strings.ToLower(level)
switch level {
case "off":
return Off
case "trace":
return Trace
case "debug":
return Debug
case "info":
return Info
case "warn":
return Warn
case "error":
return Error
case "fatal":
return Fatal
default:
return Info
}
}
// SetLevel sets the logging level of a logger.
func (l *Logger) SetLevel(level string) {
l.level = getLevel(level)
}
// IsTraceEnabled determines whether the trace level is enabled.
func (l *Logger) IsTraceEnabled() bool {
return l.level <= Trace
}
// IsDebugEnabled determines whether the debug level is enabled.
func (l *Logger) IsDebugEnabled() bool {
return l.level <= Debug
}
// IsWarnEnabled determines whether the debug level is enabled.
func (l *Logger) IsWarnEnabled() bool {
return l.level <= Warn
}
// Tracef prints trace level message with format.
func (l *Logger) Tracef(format string, v ...interface{}) {
if Trace < l.level {
return
}
l.logger.SetPrefix("T ")
l.logger.Output(3, fmt.Sprintf(format, v...))
}
// Debugf prints debug level message with format.
func (l *Logger) Debugf(format string, v ...interface{}) {
if Debug < l.level {
return
}
l.logger.SetPrefix("D ")
l.logger.Output(3, fmt.Sprintf(format, v...))
}
// Infof prints info level message with format.
func (l *Logger) Infof(format string, v ...interface{}) {
if Info < l.level {
return
}
l.logger.SetPrefix("I ")
l.logger.Output(3, fmt.Sprintf(format, v...))
}
// Warnf prints warning level message with format.
func (l *Logger) Warnf(format string, v ...interface{}) {
if Warn < l.level {
return
}
l.logger.SetPrefix("W ")
msg := fmt.Sprintf(format, v...)
l.logger.Output(3, msg)
}
// Errorf prints error level message with format.
func (l *Logger) Errorf(format string, v ...interface{}) {
if Error < l.level {
return
}
l.logger.SetPrefix("E ")
msg := fmt.Sprintf(format, v...)
l.logger.Output(3, msg)
sentry.CaptureMessage(msg)
}
// Fatalf prints fatal level message with format and exit process with code 1.
func (l *Logger) Fatalf(format string, v ...interface{}) {
if Fatal < l.level {
return
}
l.logger.SetPrefix("F ")
msg := fmt.Sprintf(format, v...)
l.logger.Output(3, msg)
sentry.CaptureMessage(msg)
closeLogger()
os.Exit(ExitCodeFatal)
}

47
kernel/util/lute.go Normal file
View file

@ -0,0 +1,47 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import "github.com/88250/lute"
func NewLute() (ret *lute.Lute) {
ret = lute.New()
ret.SetProtyleWYSIWYG(true)
ret.SetBlockRef(true)
ret.SetFileAnnotationRef(true)
ret.SetKramdownIAL(true)
ret.SetTag(true)
ret.SetSuperBlock(true)
ret.SetImgPathAllowSpace(true)
ret.SetGitConflict(true)
ret.SetMark(true)
ret.SetSup(true)
ret.SetSub(true)
ret.SetInlineMathAllowDigitAfterOpenMarker(true)
ret.SetFootnotes(false)
ret.SetToC(false)
ret.SetIndentCodeBlock(false)
ret.SetParagraphBeginningSpace(true)
ret.SetAutoSpace(false)
ret.SetHeadingID(false)
ret.SetSetext(false)
ret.SetYamlFrontMatter(false)
ret.SetLinkRef(false)
ret.SetCodeSyntaxHighlight(false)
ret.SetSanitize(true)
return
}

141
kernel/util/path.go Normal file
View file

@ -0,0 +1,141 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"bytes"
"net"
"os"
"os/exec"
"path"
"strings"
)
var (
SSL = false
UserAgent = "SiYuan/" + Ver
)
const (
ServerPort = "6806" // HTTP/WebSocket 端口
AliyunServer = "https://siyuan-sync.b3logfile.com" // 云端服务地址,阿里云负载均衡,用于接口,数据同步文件上传、下载会走七牛云 OSS http://siyuan-data.b3logfile.com
BazaarStatServer = "http://bazaar.b3logfile.com" // 集市包统计服务地址,直接对接 Bucket 没有 CDN 缓存
BazaarOSSServer = "https://oss.b3logfile.com" // 云端对象存储地址,七牛云,仅用于读取小文件(比如配置 json不用于读取包内容如果是订阅会员则用于读取包内容
BazaarOSSFileServer = "https://oss0.b3logfile.com" // 云端对象存储文件服务地址Cloudflare用于读取包内容
)
func ShortPathForBootingDisplay(p string) string {
if 25 > len(p) {
return p
}
p = strings.TrimSuffix(p, ".sy")
p = path.Base(p)
return p
}
func IsIDPattern(str string) bool {
if len("20060102150405-1a2b3c4") != len(str) {
return false
}
if 1 != strings.Count(str, "-") {
return false
}
parts := strings.Split(str, "-")
idPart := parts[0]
if 14 != len(idPart) {
return false
}
for _, c := range idPart {
if !('0' <= c && '9' >= c) {
return false
}
}
randPart := parts[1]
if 7 != len(randPart) {
return false
}
for _, c := range randPart {
if !('a' <= c && 'z' >= c) && !('0' <= c && '9' >= c) {
return false
}
}
return true
}
var LocalIPs []string
func GetLocalIPs() (ret []string) {
if 0 < len(LocalIPs) {
return LocalIPs
}
ret = []string{}
addrs, err := net.InterfaceAddrs() // Android 上用不了 https://github.com/golang/go/issues/40569所以前面使用启动内核传入的参数 localIPs
if nil != err {
LogWarnf("get interface addresses failed: %s", err)
return
}
for _, addr := range addrs {
if networkIp, ok := addr.(*net.IPNet); ok && !networkIp.IP.IsLoopback() && networkIp.IP.To4() != nil &&
bytes.Equal([]byte{255, 255, 255, 0}, networkIp.Mask) {
ret = append(ret, networkIp.IP.String())
}
}
return
}
func isRunningInDockerContainer() bool {
if _, err := os.Stat("/.dockerenv"); err == nil {
return true
}
return false
}
func IsRelativePath(dest string) bool {
if 1 > len(dest) {
return true
}
if '/' == dest[0] {
return false
}
return !strings.Contains(dest, ":/") && !strings.Contains(dest, ":\\")
}
func TimeFromID(id string) (ret string) {
ret = id[:14]
return
}
func IsValidPandocBin(binPath string) bool {
if "" == binPath {
return false
}
cmd := exec.Command(binPath, "--version")
CmdAttr(cmd)
data, err := cmd.CombinedOutput()
if nil == err && strings.HasPrefix(string(data), "pandoc") {
return true
}
return false
}

70
kernel/util/result.go Normal file
View file

@ -0,0 +1,70 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import "github.com/88250/gulu"
type PushMode int
const (
PushModeBroadcast PushMode = 0 // 广播
PushModeSingleSelf PushMode = 1 // 自我单播
PushModeBroadcastExcludeSelf PushMode = 2 // 非自我广播
PushModeBroadcastExcludeSelfApp PushMode = 4 // 非自我应用广播
PushModeNone PushMode = 10 // 不进行 reload
)
type Result struct {
Cmd string `json:"cmd"`
ReqId float64 `json:"reqId"`
AppId string `json:"app"`
SessionId string `json:"sid"`
PushMode PushMode `json:"pushMode"`
ReloadPushMode PushMode `json:"reloadPushMode"`
Callback interface{} `json:"callback"`
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func NewResult() *Result {
return &Result{Cmd: "",
ReqId: 0,
PushMode: 0,
ReloadPushMode: 0,
Callback: "",
Code: 0,
Msg: "",
Data: nil}
}
func NewCmdResult(cmdName string, cmdId float64, pushMode, reloadPushMode PushMode) *Result {
ret := NewResult()
ret.Cmd = cmdName
ret.ReqId = cmdId
ret.PushMode = pushMode
ret.ReloadPushMode = reloadPushMode
return ret
}
func (r *Result) Bytes() []byte {
ret, err := gulu.JSON.MarshalJSON(r)
if nil != err {
LogErrorf("marshal result [%+v] failed [%s]", r, err)
}
return ret
}

153
kernel/util/rhy.go Normal file
View file

@ -0,0 +1,153 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"net/http"
"sync"
"time"
"github.com/imroc/req/v3"
)
var cachedRhyResult = map[string]interface{}{}
var rhyResultCacheTime int64
var rhyResultLock = sync.Mutex{}
func GetRhyResult(force bool, proxyURL string) (map[string]interface{}, error) {
rhyResultLock.Lock()
defer rhyResultLock.Unlock()
now := time.Now().Unix()
if 3600 >= now-rhyResultCacheTime && !force {
return cachedRhyResult, nil
}
request := NewCloudRequest(proxyURL)
_, err := request.SetResult(&cachedRhyResult).Get(AliyunServer + "/apis/siyuan/version?ver=" + Ver)
if nil != err {
LogErrorf("get version meta info failed: %s", err)
return nil, err
}
rhyResultCacheTime = now
return cachedRhyResult, nil
}
var (
browserClient, browserDownloadClient, cloudAPIClient, cloudFileClientTimeout2Min, cloudFileClientTimeout15s *req.Client
browserUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36"
)
func NewBrowserRequest(proxyURL string) (ret *req.Request) {
if nil == browserClient {
browserClient = req.C().
SetUserAgent(browserUserAgent).
SetTimeout(7 * time.Second).
DisableInsecureSkipVerify()
}
if "" != proxyURL {
browserClient.SetProxyURL(proxyURL)
}
ret = browserClient.R()
ret.SetRetryCount(1).SetRetryFixedInterval(3 * time.Second)
return
}
func NewBrowserDownloadRequest(proxyURL string) *req.Request {
if nil == browserDownloadClient {
browserDownloadClient = req.C().
SetUserAgent(browserUserAgent).
SetTimeout(2 * time.Minute).
SetCommonRetryCount(1).
SetCommonRetryFixedInterval(3 * time.Second).
SetCommonRetryCondition(retryCondition).
DisableInsecureSkipVerify()
}
if "" != proxyURL {
browserDownloadClient.SetProxyURL(proxyURL)
}
return browserDownloadClient.R()
}
func NewCloudRequest(proxyURL string) *req.Request {
if nil == cloudAPIClient {
cloudAPIClient = req.C().
SetUserAgent(UserAgent).
SetTimeout(7 * time.Second).
SetCommonRetryCount(1).
SetCommonRetryFixedInterval(3 * time.Second).
SetCommonRetryCondition(retryCondition).
DisableInsecureSkipVerify()
}
if "" != proxyURL {
cloudAPIClient.SetProxyURL(proxyURL)
}
return cloudAPIClient.R()
}
func NewCloudFileRequest2m(proxyURL string) *req.Request {
if nil == cloudFileClientTimeout2Min {
cloudFileClientTimeout2Min = req.C().
SetUserAgent(UserAgent).
SetTimeout(2 * time.Minute).
SetCommonRetryCount(1).
SetCommonRetryFixedInterval(3 * time.Second).
SetCommonRetryCondition(retryCondition).
DisableInsecureSkipVerify()
setTransport(cloudFileClientTimeout2Min.GetClient())
}
if "" != proxyURL {
cloudFileClientTimeout2Min.SetProxyURL(proxyURL)
}
return cloudFileClientTimeout2Min.R()
}
func NewCloudFileRequest15s(proxyURL string) *req.Request {
if nil == cloudFileClientTimeout15s {
cloudFileClientTimeout15s = req.C().
SetUserAgent(UserAgent).
SetTimeout(15 * time.Second).
SetCommonRetryCount(1).
SetCommonRetryFixedInterval(3 * time.Second).
SetCommonRetryCondition(retryCondition).
DisableInsecureSkipVerify()
setTransport(cloudFileClientTimeout15s.GetClient())
}
if "" != proxyURL {
cloudFileClientTimeout15s.SetProxyURL(proxyURL)
}
return cloudFileClientTimeout15s.R()
}
func retryCondition(resp *req.Response, err error) bool {
if nil != err {
return true
}
if 503 == resp.StatusCode { // 负载均衡会返回 503需要重试
return true
}
return false
}
func setTransport(client *http.Client) {
// 改进同步下载数据稳定性 https://github.com/siyuan-note/siyuan/issues/4994
transport := client.Transport.(*req.Transport)
transport.MaxIdleConns = 10
transport.MaxIdleConnsPerHost = 2
transport.MaxConnsPerHost = 2
}

76
kernel/util/runtime.go Normal file
View file

@ -0,0 +1,76 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"reflect"
"runtime"
"sync"
"time"
"github.com/88250/gulu"
"github.com/denisbrodbeck/machineid"
"github.com/dustin/go-humanize"
)
const DatabaseVer = "20220501" // 修改表结构的话需要修改这里
const (
ExitCodeReadOnlyDatabase = 20 // 数据库文件被锁
ExitCodeUnavailablePort = 21 // 端口不可用
ExitCodeCreateConfDirErr = 22 // 创建配置目录失败
ExitCodeBlockTreeErr = 23 // 无法读写 blocktree.msgpack 文件
ExitCodeOk = 0 // 正常退出
ExitCodeFatal = 1 // 致命错误
)
func logBootInfo() {
s, _ := SizeOfDirectory(DataDir, true)
dataDirSize := humanize.Bytes(uint64(s))
LogInfof("kernel is booting:\n"+
" * ver [%s]\n"+
" * arch [%s]\n"+
" * runtime mode [%s]\n"+
" * working directory [%s]\n"+
" * read only [%v]\n"+
" * container [%s]\n"+
" * database [ver=%s]\n"+
" * workspace directory [%s, data %s]",
Ver, runtime.GOARCH, Mode, WorkingDir, ReadOnly, Container, DatabaseVer, WorkspaceDir, dataDirSize)
}
func IsMutexLocked(m *sync.Mutex) bool {
state := reflect.ValueOf(m).Elem().FieldByName("state")
return state.Int()&1 == 1
}
func RandomSleep(minMills, maxMills int) {
r := gulu.Rand.Int(minMills, maxMills)
time.Sleep(time.Duration(r) * time.Millisecond)
}
func GetDeviceID() string {
if "std" == Container {
machineID, err := machineid.ID()
if nil != err {
return gulu.Rand.String(12)
}
return machineID
}
return gulu.Rand.String(12)
}

59
kernel/util/session.go Normal file
View file

@ -0,0 +1,59 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"github.com/88250/gulu"
ginSessions "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
// SessionData represents the session.
type SessionData struct {
ID int
AccessAuthCode string
}
// Save saves the current session of the specified context.
func (sd *SessionData) Save(c *gin.Context) error {
session := ginSessions.Default(c)
sessionDataBytes, err := gulu.JSON.MarshalJSON(sd)
if nil != err {
return err
}
session.Set("data", string(sessionDataBytes))
return session.Save()
}
// GetSession returns session of the specified context.
func GetSession(c *gin.Context) *SessionData {
ret := &SessionData{}
session := ginSessions.Default(c)
sessionDataStr := session.Get("data")
if nil == sessionDataStr {
return ret
}
err := gulu.JSON.UnmarshalJSON([]byte(sessionDataStr.(string)), ret)
if nil != err {
return ret
}
c.Set("session", ret)
return ret
}

50
kernel/util/slice.go Normal file
View file

@ -0,0 +1,50 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import "github.com/88250/gulu"
func RemoveElem(slice []string, elem string) (ret []string) {
for _, e := range slice {
if e != elem {
ret = append(ret, e)
}
}
return
}
func ExcludeElem(slice, excludes []string) (ret []string) {
ret = []string{}
for _, e := range slice {
if !gulu.Str.Contains(e, excludes) {
ret = append(ret, e)
}
}
return
}
func RemoveDuplicatedElem(slice []string) (ret []string) {
m := map[string]bool{}
for _, str := range slice {
m[str] = true
}
ret = []string{}
for str, _ := range m {
ret = append(ret, str)
}
return
}

67
kernel/util/sort.go Normal file
View file

@ -0,0 +1,67 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"bytes"
"io"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)
func PinYinCompare(str1, str2 string) bool {
a, _ := UTF82GBK(str1)
b, _ := UTF82GBK(str2)
bLen := len(b)
for idx, chr := range a {
if idx > bLen-1 {
return false
}
if chr != b[idx] {
return chr < b[idx]
}
}
return true
}
//UTF82GBK : transform UTF8 rune into GBK byte array
func UTF82GBK(src string) ([]byte, error) {
GB18030 := simplifiedchinese.All[0]
return io.ReadAll(transform.NewReader(bytes.NewReader([]byte(src)), GB18030.NewEncoder()))
}
//GBK2UTF8 : transform GBK byte array into UTF8 string
func GBK2UTF8(src []byte) (string, error) {
GB18030 := simplifiedchinese.All[0]
bytes, err := io.ReadAll(transform.NewReader(bytes.NewReader(src), GB18030.NewDecoder()))
return string(bytes), err
}
const (
SortModeNameASC = iota // 0文件名字母升序
SortModeNameDESC // 1文件名字母降序
SortModeUpdatedASC // 2文件更新时间升序
SortModeUpdatedDESC // 3文件更新时间降序
SortModeAlphanumASC // 4文件名自然数升序
SortModeAlphanumDESC // 5文件名自然数降序
SortModeCustom // 6自定义排序
SortModeRefCountASC // 7引用数升序
SortModeRefCountDESC // 8引用数降序
SortModeCreatedASC // 9文件创建时间升序
SortModeCreatedDESC // 10文件创建时间降序
)

113
kernel/util/string.go Normal file
View file

@ -0,0 +1,113 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"strings"
"unicode"
)
func RemoveInvisible(str string) string {
str = strings.ReplaceAll(str, "\u00A0", " ") // NBSP 转换为普通空格
str = RemoveZeroWidthCharacters(str)
str = stripCtlFromUTF8(str)
return str
}
func stripCtlFromUTF8(str string) string {
return strings.Map(func(r rune) rune {
if r >= 32 && r != 127 {
return r
}
return -1
}, str)
}
const (
// ZWSP represents zero-width space.
ZWSP = '\u200B'
// ZWNBSP represents zero-width no-break space.
ZWNBSP = '\uFEFF'
// ZWJ represents zero-width joiner.
ZWJ = '\u200D'
// ZWNJ represents zero-width non-joiner.
ZWNJ = '\u200C'
empty = ""
)
var replacer = strings.NewReplacer(string(ZWSP), empty,
string(ZWNBSP), empty,
string(ZWJ), empty,
string(ZWNJ), empty)
// HasZeroWidthCharacters reports whether string s contains zero-width characters.
func HasZeroWidthCharacters(s string) bool {
return strings.ContainsRune(s, ZWSP) ||
strings.ContainsRune(s, ZWNBSP) ||
strings.ContainsRune(s, ZWJ) ||
strings.ContainsRune(s, ZWNJ)
}
// RemoveZeroWidthCharacters removes all zero-width characters from string s.
func RemoveZeroWidthCharacters(s string) string {
return replacer.Replace(s)
}
// RemoveZeroWidthSpace removes zero-width space characters from string s.
func RemoveZeroWidthSpace(s string) string {
return strings.Replace(s, string(ZWSP), empty, -1)
}
// RemoveZeroWidthNoBreakSpace removes zero-width no-break space characters from string s.
func RemoveZeroWidthNoBreakSpace(s string) string {
return strings.Replace(s, string(ZWNBSP), empty, -1)
}
// RemoveZeroWidthJoiner removes zero-width joiner characters from string s.
func RemoveZeroWidthJoiner(s string) string {
return strings.Replace(s, string(ZWJ), empty, -1)
}
// RemoveZeroWidthNonJoiner removes zero-width non-joiner characters from string s.
func RemoveZeroWidthNonJoiner(s string) string {
return strings.Replace(s, string(ZWNJ), empty, -1)
}
func IsASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] > unicode.MaxASCII {
return false
}
}
return true
}
func SubstringsBetween(str, start, end string) (ret []string) {
parts := strings.Split(str, start)
for _, p := range parts {
if !strings.Contains(p, end) {
continue
}
parts2 := strings.Split(p, end)
ret = append(ret, parts2[0])
}
return
}

35
kernel/util/time.go Normal file
View file

@ -0,0 +1,35 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"time"
)
func Millisecond2Time(t int64) time.Time {
sec := t / 1000
msec := t % 1000
return time.Unix(sec, msec*int64(time.Millisecond))
}
func CurrentTimeMillis() int64 {
return time.Now().UnixMilli()
}
func CurrentTimeSecondsStr() string {
return time.Now().Format("20060102150405")
}

84
kernel/util/web.go Normal file
View file

@ -0,0 +1,84 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"net"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/melody"
"github.com/gin-gonic/gin"
)
func GetRemoteAddr(session *melody.Session) string {
ret := session.Request.Header.Get("X-forwarded-for")
ret = strings.TrimSpace(ret)
if "" == ret {
ret = session.Request.Header.Get("X-Real-IP")
}
ret = strings.TrimSpace(ret)
if "" == ret {
return session.Request.RemoteAddr
}
return strings.Split(ret, ",")[0]
}
func JsonArg(c *gin.Context, result *gulu.Result) (arg map[string]interface{}, ok bool) {
arg = map[string]interface{}{}
if err := c.BindJSON(&arg); nil != err {
result.Code = -1
result.Msg = "parses request failed"
return
}
ok = true
return
}
func isPortOpen(port string) bool {
timeout := time.Second
conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout)
if nil != err {
return false
}
if nil != conn {
conn.Close()
return true
}
return false
}
func tryToListenPort() bool {
listener, err := net.Listen("tcp", "127.0.0.1:"+ServerPort)
if nil != err {
time.Sleep(time.Second * 3)
listener, err = net.Listen("tcp", "127.0.0.1:"+ServerPort)
if nil != err {
LogErrorf("try to listen port [%s] failed: %s", ServerPort, err)
return false
}
}
if err = listener.Close(); nil != err {
time.Sleep(time.Second * 1)
if err = listener.Close(); nil != err {
LogErrorf("close listen port [%s] failed: %s", ServerPort, err)
}
}
return true
}

269
kernel/util/websocket.go Normal file
View file

@ -0,0 +1,269 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"sync"
"github.com/88250/melody"
)
var (
WebSocketServer = melody.New()
// map[string]map[string]*melody.Session{}
sessions = sync.Map{} // {appId, {sessionId, session}}
)
// BroadcastByType 广播所有实例上 typ 类型的会话。
func BroadcastByType(typ, cmd string, code int, msg string, data interface{}) {
typeSessions := SessionsByType(typ)
for _, sess := range typeSessions {
event := NewResult()
event.Cmd = cmd
event.Code = code
event.Msg = msg
event.Data = data
sess.Write(event.Bytes())
}
}
func SessionsByType(typ string) (ret []*melody.Session) {
ret = []*melody.Session{}
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
if t, ok := session.Get("type"); ok && typ == t {
ret = append(ret, session)
}
return true
})
return true
})
return
}
func AddPushChan(session *melody.Session) {
appID := session.Request.URL.Query().Get("app")
session.Set("app", appID)
id := session.Request.URL.Query().Get("id")
session.Set("id", id)
typ := session.Request.URL.Query().Get("type")
session.Set("type", typ)
if appSessions, ok := sessions.Load(appID); !ok {
appSess := &sync.Map{}
appSess.Store(id, session)
sessions.Store(appID, appSess)
} else {
(appSessions.(*sync.Map)).Store(id, session)
}
}
func RemovePushChan(session *melody.Session) {
app, _ := session.Get("app")
id, _ := session.Get("id")
if nil == app || nil == id {
return
}
appSess, _ := sessions.Load(app)
if nil != appSess {
appSessions := appSess.(*sync.Map)
appSessions.Delete(id)
if 1 > lenOfSyncMap(appSessions) {
sessions.Delete(app)
}
}
}
func lenOfSyncMap(m *sync.Map) (ret int) {
m.Range(func(key, value interface{}) bool {
ret++
return true
})
return
}
func ClosePushChan(id string) {
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
if sid, _ := session.Get("id"); sid == id {
session.CloseWithMsg([]byte(" close websocket"))
RemovePushChan(session)
}
return true
})
return true
})
}
func ReloadUI() {
evt := NewCmdResult("reloadui", 0, PushModeBroadcast, 0)
PushEvent(evt)
}
func PushTxErr(msg string, code int, data interface{}) {
BroadcastByType("main", "txerr", code, msg, data)
}
func PushMsg(msg string, timeout int) {
evt := NewCmdResult("msg", 0, PushModeBroadcast, 0)
evt.Msg = msg
evt.Data = map[string]interface{}{"closeTimeout": timeout}
PushEvent(evt)
}
func PushErrMsg(msg string, timeout int) {
evt := NewCmdResult("msg", 0, PushModeBroadcast, 0)
evt.Code = -1
evt.Msg = msg
evt.Data = map[string]interface{}{"closeTimeout": timeout}
PushEvent(evt)
}
const (
PushProgressCodeProgressed = 0 // 有进度
PushProgressCodeEndless = 1 // 无进度
PushProgressCodeEnd = 2 // 关闭进度
)
func ClearPushProgress(total int) {
PushProgress(PushProgressCodeEnd, total, total, "")
}
func PushEndlessProgress(msg string) {
PushProgress(PushProgressCodeEndless, 1, 1, msg)
}
func PushProgress(code, current, total int, msg string) {
evt := NewCmdResult("progress", 0, PushModeBroadcast, 0)
evt.Msg = msg
evt.Code = code
evt.Data = map[string]interface{}{
"current": current,
"total": total,
}
PushEvent(evt)
}
// PushClearMsg 会清空消息提示以及进度遮罩。
func PushClearMsg() {
evt := NewCmdResult("cmsg", 0, PushModeBroadcast, 0)
PushEvent(evt)
}
func PushDownloadProgress(id string, percent float32) {
evt := NewCmdResult("downloadProgress", 0, PushModeBroadcast, 0)
evt.Data = map[string]interface{}{
"id": id,
"percent": percent,
}
PushEvent(evt)
}
func PushEvent(event *Result) {
msg := event.Bytes()
mode := event.PushMode
if "reload" == event.Cmd {
mode = event.ReloadPushMode
}
switch mode {
case PushModeBroadcast:
Broadcast(msg)
case PushModeSingleSelf:
single(msg, event.AppId, event.SessionId)
case PushModeBroadcastExcludeSelf:
broadcastOthers(msg, event.SessionId)
case PushModeBroadcastExcludeSelfApp:
broadcastOtherApps(msg, event.AppId)
case PushModeNone:
}
}
func single(msg []byte, appId, sid string) {
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
if key != appId {
return true
}
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
if id, _ := session.Get("id"); id == sid {
session.Write(msg)
}
return true
})
return true
})
}
func Broadcast(msg []byte) {
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
session.Write(msg)
return true
})
return true
})
}
func broadcastOtherApps(msg []byte, excludeApp string) {
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
if app, _ := session.Get("app"); app == excludeApp {
return true
}
session.Write(msg)
return true
})
return true
})
}
func broadcastOthers(msg []byte, excludeSID string) {
sessions.Range(func(key, value interface{}) bool {
appSessions := value.(*sync.Map)
appSessions.Range(func(key, value interface{}) bool {
session := value.(*melody.Session)
if id, _ := session.Get("id"); id == excludeSID {
return true
}
session.Write(msg)
return true
})
return true
})
}
func CountSessions() (ret int) {
sessions.Range(func(key, value interface{}) bool {
ret++
return true
})
return
}

434
kernel/util/working.go Normal file
View file

@ -0,0 +1,434 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"flag"
"log"
"math/rand"
"mime"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
figure "github.com/common-nighthawk/go-figure"
goPS "github.com/mitchellh/go-ps"
)
var Mode = "dev"
//var Mode = "prod"
const (
Ver = "2.0.13"
IsInsider = false
)
var (
bootProgress float64 // 启动进度,从 0 到 100
bootDetails string // 启动细节描述
HttpServing = false // 是否 HTTP 伺服已经可用
)
func Boot() {
IncBootProgress(3, "Booting...")
rand.Seed(time.Now().UTC().UnixNano())
initMime()
workspacePath := flag.String("workspace", "", "dir path of the workspace, default to ~/Documents/SiYuan/")
wdPath := flag.String("wd", WorkingDir, "working directory of SiYuan")
servePath := flag.String("servePath", "", "obsoleted https://github.com/siyuan-note/siyuan/issues/4647")
_ = servePath
resident := flag.Bool("resident", true, "resident memory even if no active session")
readOnly := flag.Bool("readonly", false, "read-only mode")
accessAuthCode := flag.String("accessAuthCode", "", "access auth code")
ssl := flag.Bool("ssl", false, "for https and wss")
lang := flag.String("lang", "en_US", "zh_CN/zh_CHT/en_US/fr_FR")
flag.Parse()
if "" != *wdPath {
WorkingDir = *wdPath
}
if "" != *lang {
Lang = *lang
}
Resident = *resident
ReadOnly = *readOnly
AccessAuthCode = *accessAuthCode
Container = "std"
if isRunningInDockerContainer() {
Container = "docker"
}
initWorkspaceDir(*workspacePath)
SSL = *ssl
LogPath = filepath.Join(TempDir, "siyuan.log")
AppearancePath = filepath.Join(ConfDir, "appearance")
if "dev" == Mode {
ThemesPath = filepath.Join(WorkingDir, "appearance", "themes")
IconsPath = filepath.Join(WorkingDir, "appearance", "icons")
} else {
ThemesPath = filepath.Join(AppearancePath, "themes")
IconsPath = filepath.Join(AppearancePath, "icons")
}
initPathDir()
checkPort()
bootBanner := figure.NewColorFigure("SiYuan", "isometric3", "green", true)
LogInfof("\n" + bootBanner.String())
logBootInfo()
go cleanOld()
}
func SetBootDetails(details string) {
if 100 <= bootProgress {
return
}
bootDetails = details
}
func IncBootProgress(progress float64, details string) {
if 100 <= bootProgress {
return
}
bootProgress += progress
bootDetails = details
}
func IsBooted() bool {
return 100 <= bootProgress
}
func GetBootProgressDetails() (float64, string) {
return bootProgress, bootDetails
}
func GetBootProgress() float64 {
return bootProgress
}
func SetBooted() {
bootDetails = "Finishing boot..."
bootProgress = 100
LogInfof("kernel booted")
}
func GetHistoryDirNow(now, suffix string) (ret string, err error) {
ret = filepath.Join(WorkspaceDir, "history", now+"-"+suffix)
if err = os.MkdirAll(ret, 0755); nil != err {
LogErrorf("make history dir failed: %s", err)
return
}
return
}
func GetHistoryDir(suffix string) (ret string, err error) {
ret = filepath.Join(WorkspaceDir, "history", time.Now().Format("2006-01-02-150405")+"-"+suffix)
if err = os.MkdirAll(ret, 0755); nil != err {
LogErrorf("make history dir failed: %s", err)
return
}
return
}
var (
HomeDir, _ = gulu.OS.Home()
WorkingDir, _ = os.Getwd()
WorkspaceDir string // 工作空间目录路径
ConfDir string // 配置目录路径
DataDir string // 数据目录路径
TempDir string // 临时目录路径
LogPath string // 配置目录下的日志文件 siyuan.log 路径
DBName = "siyuan.db" // SQLite 数据库文件名
DBPath string // SQLite 数据库文件路径
BlockTreePath string // 区块树文件路径
AppearancePath string // 配置目录下的外观目录 appearance/ 路径
ThemesPath string // 配置目录下的外观目录下的 themes/ 路径
IconsPath string // 配置目录下的外观目录下的 icons/ 路径
AndroidNativeLibDir string // Android 库路径
AndroidPrivateDataDir string // Android 私有数据路径
UIProcessIDs = sync.Map{} // UI 进程 ID
IsNewbie bool // 是否是第一次安装
)
func initWorkspaceDir(workspaceArg string) {
userHomeConfDir := filepath.Join(HomeDir, ".config", "siyuan")
workspaceConf := filepath.Join(userHomeConfDir, "workspace.json")
if !gulu.File.IsExist(workspaceConf) {
IsNewbie = "std" == Container // 只有桌面端需要设置新手标识,前端自动挂载帮助文档
if err := os.MkdirAll(userHomeConfDir, 0755); nil != err && !os.IsExist(err) {
log.Printf("create user home conf folder [%s] failed: %s", userHomeConfDir, err)
os.Exit(ExitCodeCreateConfDirErr)
}
}
defaultWorkspaceDir := filepath.Join(HomeDir, "Documents", "SiYuan")
var workspacePaths []string
if !gulu.File.IsExist(workspaceConf) {
WorkspaceDir = defaultWorkspaceDir
if "" != workspaceArg {
WorkspaceDir = workspaceArg
}
if !gulu.File.IsDir(WorkspaceDir) {
log.Printf("use the default workspace [%s] since the specified workspace [%s] is not a dir", WorkspaceDir, defaultWorkspaceDir)
WorkspaceDir = defaultWorkspaceDir
}
workspacePaths = append(workspacePaths, WorkspaceDir)
} else {
data, err := os.ReadFile(workspaceConf)
if err = gulu.JSON.UnmarshalJSON(data, &workspacePaths); nil != err {
log.Printf("unmarshal workspace conf [%s] failed: %s", workspaceConf, err)
}
tmp := workspacePaths[:0]
for _, d := range workspacePaths {
if gulu.File.IsDir(d) {
tmp = append(tmp, d)
}
}
workspacePaths = tmp
if 0 < len(workspacePaths) {
WorkspaceDir = workspacePaths[len(workspacePaths)-1]
if "" != workspaceArg {
WorkspaceDir = workspaceArg
}
if !gulu.File.IsDir(WorkspaceDir) {
log.Printf("use the default workspace [%s] since the specified workspace [%s] is not a dir", WorkspaceDir, defaultWorkspaceDir)
WorkspaceDir = defaultWorkspaceDir
}
workspacePaths[len(workspacePaths)-1] = WorkspaceDir
} else {
WorkspaceDir = defaultWorkspaceDir
if "" != workspaceArg {
WorkspaceDir = workspaceArg
}
if !gulu.File.IsDir(WorkspaceDir) {
log.Printf("use the default workspace [%s] since the specified workspace [%s] is not a dir", WorkspaceDir, defaultWorkspaceDir)
WorkspaceDir = defaultWorkspaceDir
}
workspacePaths = append(workspacePaths, WorkspaceDir)
}
}
if data, err := gulu.JSON.MarshalJSON(workspacePaths); nil == err {
if err = os.WriteFile(workspaceConf, data, 0644); nil != err {
log.Fatalf("write workspace conf [%s] failed: %s", workspaceConf, err)
}
} else {
log.Fatalf("marshal workspace conf [%s] failed: %s", workspaceConf, err)
}
ConfDir = filepath.Join(WorkspaceDir, "conf")
DataDir = filepath.Join(WorkspaceDir, "data")
TempDir = filepath.Join(WorkspaceDir, "temp")
DBPath = filepath.Join(TempDir, DBName)
BlockTreePath = filepath.Join(TempDir, "blocktree.msgpack")
}
var (
Resident bool
ReadOnly bool
AccessAuthCode string
Lang = "en_US"
Container string // docker, android, ios, std
)
func initPathDir() {
if err := os.MkdirAll(ConfDir, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create conf folder [%s] failed: %s", ConfDir, err)
}
if err := os.MkdirAll(DataDir, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create data folder [%s] failed: %s", DataDir, err)
}
if err := os.MkdirAll(TempDir, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create temp folder [%s] failed: %s", TempDir, err)
}
assets := filepath.Join(DataDir, "assets")
if err := os.MkdirAll(assets, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create data assets folder [%s] failed: %s", assets, err)
}
templates := filepath.Join(DataDir, "templates")
if err := os.MkdirAll(templates, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create data templates folder [%s] failed: %s", templates, err)
}
widgets := filepath.Join(DataDir, "widgets")
if err := os.MkdirAll(widgets, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create data widgets folder [%s] failed: %s", widgets, err)
}
emojis := filepath.Join(DataDir, "emojis")
if err := os.MkdirAll(emojis, 0755); nil != err && !os.IsExist(err) {
log.Fatalf("create data emojis folder [%s] failed: %s", widgets, err)
}
}
func cleanOld() {
dirs, _ := os.ReadDir(WorkingDir)
for _, dir := range dirs {
if strings.HasSuffix(dir.Name(), ".old") {
old := filepath.Join(WorkingDir, dir.Name())
os.RemoveAll(old)
}
}
}
func checkPort() {
portOpened := isPortOpen(ServerPort)
if !portOpened {
return
}
LogInfof("port [%s] is opened, try to check version of running kernel", ServerPort)
result := NewResult()
_, err := NewBrowserRequest("").
SetResult(result).
SetHeader("User-Agent", UserAgent).
Get("http://127.0.0.1:" + ServerPort + "/api/system/version")
if nil != err || 0 != result.Code {
LogErrorf("connect to port [%s] for checking running kernel failed", ServerPort)
KillByPort(ServerPort)
return
}
if nil == result.Data {
LogErrorf("connect ot port [%s] for checking running kernel failed", ServerPort)
os.Exit(ExitCodeUnavailablePort)
}
runningVer := result.Data.(string)
if runningVer == Ver {
LogInfof("version of the running kernel is the same as this boot [%s], exit this boot", runningVer)
os.Exit(ExitCodeOk)
}
LogInfof("found kernel [%s] is running, try to exit it", runningVer)
processes, err := goPS.Processes()
if nil != err {
LogErrorf("close kernel [%s] failed: %s", runningVer, err)
os.Exit(ExitCodeUnavailablePort)
}
currentPid := os.Getpid()
for _, p := range processes {
name := p.Executable()
if strings.Contains(strings.ToLower(name), "siyuan-kernel") || strings.Contains(strings.ToLower(name), "siyuan kernel") {
kernelPid := p.Pid()
if currentPid != kernelPid {
pid := strconv.Itoa(kernelPid)
Kill(pid)
LogInfof("killed kernel [name=%s, pid=%s, ver=%s], continue to boot", name, pid, runningVer)
}
}
}
if !tryToListenPort() {
os.Exit(ExitCodeUnavailablePort)
}
}
func initMime() {
// 在某版本的 Windows 10 操作系统上界面样式异常问题
// https://github.com/siyuan-note/siyuan/issues/247
// https://github.com/siyuan-note/siyuan/issues/3813
mime.AddExtensionType(".css", "text/css")
mime.AddExtensionType(".js", "application/x-javascript")
mime.AddExtensionType(".json", "application/json")
mime.AddExtensionType(".html", "text/html")
}
func KillByPort(port string) {
if pid := PidByPort(port); "" != pid {
pidInt, _ := strconv.Atoi(pid)
proc, _ := goPS.FindProcess(pidInt)
var name string
if nil != proc {
name = proc.Executable()
}
Kill(pid)
LogInfof("killed process [name=%s, pid=%s]", name, pid)
}
}
func Kill(pid string) {
var kill *exec.Cmd
if gulu.OS.IsWindows() {
kill = exec.Command("cmd", "/c", "TASKKILL /F /PID "+pid)
} else {
kill = exec.Command("kill", "-9", pid)
}
CmdAttr(kill)
kill.CombinedOutput()
}
func PidByPort(port string) (ret string) {
if gulu.OS.IsWindows() {
cmd := exec.Command("cmd", "/c", "netstat -ano | findstr "+port)
CmdAttr(cmd)
data, err := cmd.CombinedOutput()
if nil != err {
LogErrorf("netstat failed: %s", err)
return
}
output := string(data)
lines := strings.Split(output, "\n")
for _, l := range lines {
if strings.Contains(l, "LISTENING") {
l = l[strings.Index(l, "LISTENING")+len("LISTENING"):]
l = strings.TrimSpace(l)
ret = l
return
}
}
return
}
cmd := exec.Command("lsof", "-Fp", "-i", ":"+port)
CmdAttr(cmd)
data, err := cmd.CombinedOutput()
if nil != err {
LogErrorf("lsof failed: %s", err)
return
}
output := string(data)
lines := strings.Split(output, "\n")
for _, l := range lines {
if strings.HasPrefix(l, "p") {
l = l[1:]
ret = l
return
}
}
return
}

View file

@ -0,0 +1,53 @@
// SiYuan - Build Your Eternal Digital Garden
// 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 util
import (
"math/rand"
"path/filepath"
"time"
figure "github.com/common-nighthawk/go-figure"
)
func BootMobile(container, appDir, workspaceDir, nativeLibDir, privateDataDir, lang string) {
IncBootProgress(3, "Booting...")
rand.Seed(time.Now().UTC().UnixNano())
initMime()
HomeDir = filepath.Join(workspaceDir, "home")
WorkingDir = filepath.Join(appDir, "app")
WorkspaceDir = workspaceDir
ConfDir = filepath.Join(workspaceDir, "conf")
DataDir = filepath.Join(workspaceDir, "data")
TempDir = filepath.Join(workspaceDir, "temp")
DBPath = filepath.Join(TempDir, DBName)
BlockTreePath = filepath.Join(TempDir, "blocktree.msgpack")
AndroidNativeLibDir = nativeLibDir
AndroidPrivateDataDir = privateDataDir
LogPath = filepath.Join(TempDir, "siyuan.log")
AppearancePath = filepath.Join(ConfDir, "appearance")
ThemesPath = filepath.Join(AppearancePath, "themes")
IconsPath = filepath.Join(AppearancePath, "icons")
Resident = true
Container = container
Lang = lang
initPathDir()
bootBanner := figure.NewFigure("SiYuan", "", true)
LogInfof("\n" + bootBanner.String())
logBootInfo()
}