mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-03-08 05:32:33 +01:00
🎨 Unified file listener logic (#17134)
This commit is contained in:
parent
ba733eedfd
commit
26c378a820
10 changed files with 462 additions and 191 deletions
|
|
@ -21,11 +21,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/siyuan-note/filelock"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/bazaar"
|
||||
|
|
@ -41,15 +39,17 @@ func InitAppearance() {
|
|||
return
|
||||
}
|
||||
|
||||
unloadThemes()
|
||||
from := filepath.Join(util.WorkingDir, "appearance")
|
||||
if err := filelock.Copy(from, util.AppearancePath); err != nil {
|
||||
logging.LogErrorf("copy appearance resources from [%s] to [%s] failed: %s", from, util.AppearancePath, err)
|
||||
util.ReportFileSysFatalError(err)
|
||||
return
|
||||
}
|
||||
loadThemes()
|
||||
|
||||
loadThemes()
|
||||
LoadIcons()
|
||||
|
||||
Conf.m.Lock()
|
||||
if !containTheme(Conf.Appearance.ThemeDark, Conf.Appearance.DarkThemes) {
|
||||
Conf.Appearance.ThemeDark = "midnight"
|
||||
Conf.Appearance.ThemeJS = false
|
||||
|
|
@ -58,11 +58,10 @@ func InitAppearance() {
|
|||
Conf.Appearance.ThemeLight = "daylight"
|
||||
Conf.Appearance.ThemeJS = false
|
||||
}
|
||||
|
||||
loadIcons()
|
||||
if !gulu.Str.Contains(Conf.Appearance.Icon, Conf.Appearance.Icons) {
|
||||
Conf.Appearance.Icon = "material"
|
||||
}
|
||||
Conf.m.Unlock()
|
||||
|
||||
Conf.Save()
|
||||
|
||||
|
|
@ -78,36 +77,6 @@ func containTheme(name string, themes []*conf.AppearanceTheme) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
var themeWatchers = sync.Map{} // [string]*fsnotify.Watcher{}
|
||||
|
||||
func closeThemeWatchers() {
|
||||
themeWatchers.Range(func(key, value interface{}) bool {
|
||||
if err := value.(*fsnotify.Watcher).Close(); err != nil {
|
||||
logging.LogErrorf("close file watcher failed: %s", err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func unloadThemes() {
|
||||
if !util.IsPathRegularDirOrSymlinkDir(util.ThemesPath) {
|
||||
return
|
||||
}
|
||||
|
||||
themeDirs, err := os.ReadDir(util.ThemesPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read appearance themes folder failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, themeDir := range themeDirs {
|
||||
if !util.IsDirRegularOrSymlink(themeDir) {
|
||||
continue
|
||||
}
|
||||
unwatchTheme(filepath.Join(util.ThemesPath, themeDir.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func loadThemes() {
|
||||
themeDirs, err := os.ReadDir(util.ThemesPath)
|
||||
if err != nil {
|
||||
|
|
@ -116,9 +85,13 @@ func loadThemes() {
|
|||
return
|
||||
}
|
||||
|
||||
Conf.Appearance.DarkThemes = nil
|
||||
Conf.Appearance.LightThemes = nil
|
||||
var darkThemes, lightThemes []*conf.AppearanceTheme
|
||||
var daylightTheme, midnightTheme *conf.AppearanceTheme
|
||||
var themeVer string
|
||||
var themeJS bool
|
||||
mode := Conf.Appearance.Mode
|
||||
themeLight := Conf.Appearance.ThemeLight
|
||||
themeDark := Conf.Appearance.ThemeDark
|
||||
for _, themeDir := range themeDirs {
|
||||
if !util.IsDirRegularOrSymlink(themeDir) {
|
||||
continue
|
||||
|
|
@ -129,10 +102,9 @@ func loadThemes() {
|
|||
continue
|
||||
}
|
||||
|
||||
modes := themeConf.Modes
|
||||
for _, mode := range modes {
|
||||
for _, mode := range themeConf.Modes {
|
||||
t := &conf.AppearanceTheme{Name: name}
|
||||
if "midnight" == name || "daylight" == name {
|
||||
if isBuiltInTheme(name) {
|
||||
t.Label = name + Conf.Language(281)
|
||||
} else {
|
||||
t.Label = name
|
||||
|
|
@ -156,32 +128,37 @@ func loadThemes() {
|
|||
}
|
||||
|
||||
if "dark" == mode {
|
||||
Conf.Appearance.DarkThemes = append(Conf.Appearance.DarkThemes, t)
|
||||
darkThemes = append(darkThemes, t)
|
||||
} else if "light" == mode {
|
||||
Conf.Appearance.LightThemes = append(Conf.Appearance.LightThemes, t)
|
||||
lightThemes = append(lightThemes, t)
|
||||
}
|
||||
}
|
||||
|
||||
if 0 == Conf.Appearance.Mode {
|
||||
if Conf.Appearance.ThemeLight == name {
|
||||
Conf.Appearance.ThemeVer = themeConf.Version
|
||||
Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js"))
|
||||
if 0 == mode {
|
||||
if themeLight == name {
|
||||
themeVer = themeConf.Version
|
||||
themeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js"))
|
||||
}
|
||||
} else {
|
||||
if Conf.Appearance.ThemeDark == name {
|
||||
Conf.Appearance.ThemeVer = themeConf.Version
|
||||
Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js"))
|
||||
if themeDark == name {
|
||||
themeVer = themeConf.Version
|
||||
themeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js"))
|
||||
}
|
||||
}
|
||||
|
||||
go watchTheme(filepath.Join(util.ThemesPath, name))
|
||||
}
|
||||
|
||||
Conf.Appearance.LightThemes = append([]*conf.AppearanceTheme{daylightTheme}, Conf.Appearance.LightThemes...)
|
||||
Conf.Appearance.DarkThemes = append([]*conf.AppearanceTheme{midnightTheme}, Conf.Appearance.DarkThemes...)
|
||||
lightThemes = append([]*conf.AppearanceTheme{daylightTheme}, lightThemes...)
|
||||
darkThemes = append([]*conf.AppearanceTheme{midnightTheme}, darkThemes...)
|
||||
|
||||
Conf.m.Lock()
|
||||
Conf.Appearance.DarkThemes = darkThemes
|
||||
Conf.Appearance.LightThemes = lightThemes
|
||||
Conf.Appearance.ThemeVer = themeVer
|
||||
Conf.Appearance.ThemeJS = themeJS
|
||||
Conf.m.Unlock()
|
||||
}
|
||||
|
||||
func loadIcons() {
|
||||
func LoadIcons() {
|
||||
iconDirs, err := os.ReadDir(util.IconsPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read appearance icons folder failed: %s", err)
|
||||
|
|
@ -189,7 +166,9 @@ func loadIcons() {
|
|||
return
|
||||
}
|
||||
|
||||
Conf.Appearance.Icons = nil
|
||||
var icons []string
|
||||
var iconVer string
|
||||
currentIcon := Conf.Appearance.Icon
|
||||
for _, iconDir := range iconDirs {
|
||||
if !util.IsDirRegularOrSymlink(iconDir) {
|
||||
continue
|
||||
|
|
@ -199,77 +178,15 @@ func loadIcons() {
|
|||
if err != nil || nil == iconConf {
|
||||
continue
|
||||
}
|
||||
Conf.Appearance.Icons = append(Conf.Appearance.Icons, name)
|
||||
if Conf.Appearance.Icon == name {
|
||||
Conf.Appearance.IconVer = iconConf.Version
|
||||
icons = append(icons, name)
|
||||
if currentIcon == name {
|
||||
iconVer = iconConf.Version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReloadIcon() {
|
||||
loadIcons()
|
||||
}
|
||||
|
||||
func unwatchTheme(folder string) {
|
||||
val, _ := themeWatchers.Load(folder)
|
||||
if nil != val {
|
||||
themeWatcher := val.(*fsnotify.Watcher)
|
||||
themeWatcher.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func watchTheme(folder string) {
|
||||
val, _ := themeWatchers.Load(folder)
|
||||
var themeWatcher *fsnotify.Watcher
|
||||
if nil != val {
|
||||
themeWatcher = val.(*fsnotify.Watcher)
|
||||
themeWatcher.Close()
|
||||
}
|
||||
|
||||
var err error
|
||||
if themeWatcher, err = fsnotify.NewWatcher(); err != nil {
|
||||
logging.LogErrorf("add theme file watcher for folder [%s] failed: %s", folder, err)
|
||||
return
|
||||
}
|
||||
themeWatchers.Store(folder, themeWatcher)
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-themeWatcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
//logging.LogInfof(event.String())
|
||||
if event.Op&fsnotify.Write == fsnotify.Write && (strings.HasSuffix(event.Name, "theme.css")) {
|
||||
var themeName string
|
||||
if themeName = isCurrentUseTheme(event.Name); "" == themeName {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.HasSuffix(event.Name, "theme.css") {
|
||||
util.BroadcastByType("main", "refreshtheme", 0, "", map[string]interface{}{
|
||||
"theme": "/appearance/themes/" + themeName + "/theme.css?" + fmt.Sprintf("%d", time.Now().Unix()),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
case err, ok := <-themeWatcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
logging.LogErrorf("watch theme file failed: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//logging.LogInfof("add file watcher [%s]", folder)
|
||||
if err := themeWatcher.Add(folder); err != nil {
|
||||
logging.LogErrorf("add theme files watcher for folder [%s] failed: %s", folder, err)
|
||||
}
|
||||
<-done
|
||||
Conf.m.Lock()
|
||||
Conf.Appearance.Icons = icons
|
||||
Conf.Appearance.IconVer = iconVer
|
||||
Conf.m.Unlock()
|
||||
}
|
||||
|
||||
func isCurrentUseTheme(themePath string) string {
|
||||
|
|
@ -285,3 +202,22 @@ func isCurrentUseTheme(themePath string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func broadcastRefreshThemeIfCurrent(themeCssPath string) {
|
||||
if !strings.HasSuffix(themeCssPath, "theme.css") {
|
||||
return
|
||||
}
|
||||
// 只处理主题根目录中的 theme.css
|
||||
themeDir := filepath.Clean(filepath.Dir(themeCssPath))
|
||||
themesRoot := filepath.Clean(util.ThemesPath)
|
||||
if themeDir != filepath.Join(themesRoot, filepath.Base(themeDir)) {
|
||||
return
|
||||
}
|
||||
themeName := isCurrentUseTheme(themeCssPath)
|
||||
if themeName == "" {
|
||||
return
|
||||
}
|
||||
util.BroadcastByType("main", "refreshtheme", 0, "", map[string]interface{}{
|
||||
"theme": "/appearance/themes/" + themeName + "/theme.css?" + fmt.Sprintf("%d", time.Now().Unix()),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,23 +37,30 @@ func WatchAssets() {
|
|||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
watchAssets()
|
||||
}()
|
||||
go watchAssets()
|
||||
}
|
||||
|
||||
func watchAssets() {
|
||||
CloseWatchAssets()
|
||||
assetsDir := filepath.Join(util.DataDir, "assets")
|
||||
if nil != assetsWatcher {
|
||||
assetsWatcher.Close()
|
||||
}
|
||||
|
||||
var err error
|
||||
if assetsWatcher, err = fsnotify.NewWatcher(); err != nil {
|
||||
assetsWatcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logging.LogErrorf("add assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !gulu.File.IsDir(assetsDir) {
|
||||
os.MkdirAll(assetsDir, 0755)
|
||||
}
|
||||
|
||||
if err = assetsWatcher.Add(assetsDir); err != nil {
|
||||
logging.LogErrorf("add assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
CloseWatchAssets()
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
|
|
@ -95,19 +102,11 @@ func watchAssets() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if !gulu.File.IsDir(assetsDir) {
|
||||
os.MkdirAll(assetsDir, 0755)
|
||||
}
|
||||
|
||||
if err = assetsWatcher.Add(assetsDir); err != nil {
|
||||
logging.LogErrorf("add assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
}
|
||||
//logging.LogInfof("added file watcher [%s]", assetsDir)
|
||||
}
|
||||
|
||||
func CloseWatchAssets() {
|
||||
if nil != assetsWatcher {
|
||||
assetsWatcher.Close()
|
||||
assetsWatcher = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/radovskyb/watcher"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/cache"
|
||||
|
|
@ -31,20 +33,31 @@ import (
|
|||
var assetsWatcher *watcher.Watcher
|
||||
|
||||
func WatchAssets() {
|
||||
go func() {
|
||||
watchAssets()
|
||||
}()
|
||||
if !isFileWatcherAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
go watchAssets()
|
||||
}
|
||||
|
||||
func watchAssets() {
|
||||
if nil != assetsWatcher {
|
||||
assetsWatcher.Close()
|
||||
}
|
||||
assetsWatcher = watcher.New()
|
||||
|
||||
CloseWatchAssets()
|
||||
assetsDir := filepath.Join(util.DataDir, "assets")
|
||||
|
||||
assetsWatcher = watcher.New()
|
||||
|
||||
if !gulu.File.IsDir(assetsDir) {
|
||||
os.MkdirAll(assetsDir, 0755)
|
||||
}
|
||||
|
||||
if err := assetsWatcher.Add(assetsDir); err != nil {
|
||||
logging.LogErrorf("add assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-assetsWatcher.Event:
|
||||
|
|
@ -75,13 +88,6 @@ func watchAssets() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := assetsWatcher.Add(assetsDir); err != nil {
|
||||
logging.LogErrorf("add assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
//logging.LogInfof("added file watcher [%s]", assetsDir)
|
||||
if err := assetsWatcher.Start(10 * time.Second); err != nil {
|
||||
logging.LogErrorf("start assets watcher for folder [%s] failed: %s", assetsDir, err)
|
||||
return
|
||||
|
|
@ -91,5 +97,6 @@ func watchAssets() {
|
|||
func CloseWatchAssets() {
|
||||
if nil != assetsWatcher {
|
||||
assetsWatcher.Close()
|
||||
assetsWatcher = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ func InstalledThemes(keyword string) (ret []*bazaar.Theme) {
|
|||
}
|
||||
|
||||
func InstallBazaarTheme(repoURL, repoHash, themeName string, mode int, update bool) error {
|
||||
closeThemeWatchers()
|
||||
CloseWatchThemes()
|
||||
|
||||
installPath := filepath.Join(util.ThemesPath, themeName)
|
||||
err := bazaar.InstallTheme(repoURL, repoHash, installPath, Conf.System.ID)
|
||||
|
|
@ -458,7 +458,7 @@ func InstallBazaarTheme(repoURL, repoHash, themeName string, mode int, update bo
|
|||
}
|
||||
|
||||
func UninstallBazaarTheme(themeName string) error {
|
||||
closeThemeWatchers()
|
||||
CloseWatchThemes()
|
||||
|
||||
installPath := filepath.Join(util.ThemesPath, themeName)
|
||||
err := bazaar.UninstallTheme(installPath)
|
||||
|
|
@ -580,3 +580,8 @@ func getSearchKeywords(query string) (ret []string) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isBuiltInTheme 通过包名或目录名判断是否为内置主题
|
||||
func isBuiltInTheme(name string) bool {
|
||||
return "daylight" == name || "midnight" == name
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,27 +32,34 @@ import (
|
|||
var emojisWatcher *fsnotify.Watcher
|
||||
|
||||
func WatchEmojis() {
|
||||
if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container || util.ContainerHarmony == util.Container {
|
||||
if !isFileWatcherAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
watchEmojis()
|
||||
}()
|
||||
go watchEmojis()
|
||||
}
|
||||
|
||||
func watchEmojis() {
|
||||
CloseWatchEmojis()
|
||||
emojisDir := filepath.Join(util.DataDir, "emojis")
|
||||
if nil != emojisWatcher {
|
||||
emojisWatcher.Close()
|
||||
}
|
||||
|
||||
var err error
|
||||
if emojisWatcher, err = fsnotify.NewWatcher(); err != nil {
|
||||
emojisWatcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logging.LogErrorf("add emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !gulu.File.IsDir(emojisDir) {
|
||||
os.MkdirAll(emojisDir, 0755)
|
||||
}
|
||||
|
||||
if err = emojisWatcher.Add(emojisDir); err != nil {
|
||||
logging.LogErrorf("add emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
CloseWatchEmojis()
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
|
|
@ -77,19 +84,11 @@ func watchEmojis() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if !gulu.File.IsDir(emojisDir) {
|
||||
os.MkdirAll(emojisDir, 0755)
|
||||
}
|
||||
|
||||
if err = emojisWatcher.Add(emojisDir); err != nil {
|
||||
logging.LogErrorf("add emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
}
|
||||
//logging.LogInfof("added file watcher [%s]", emojisDir)
|
||||
}
|
||||
|
||||
func CloseWatchEmojis() {
|
||||
if nil != emojisWatcher {
|
||||
emojisWatcher.Close()
|
||||
emojisWatcher = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/radovskyb/watcher"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
|
|
@ -30,20 +32,31 @@ import (
|
|||
var emojisWatcher *watcher.Watcher
|
||||
|
||||
func WatchEmojis() {
|
||||
go func() {
|
||||
watchEmojis()
|
||||
}()
|
||||
if !isFileWatcherAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
go watchEmojis()
|
||||
}
|
||||
|
||||
func watchEmojis() {
|
||||
if nil != emojisWatcher {
|
||||
emojisWatcher.Close()
|
||||
}
|
||||
emojisWatcher = watcher.New()
|
||||
|
||||
emojisDir := filepath.Join(util.DataDir, "emojis")
|
||||
|
||||
CloseWatchEmojis()
|
||||
emojisWatcher = watcher.New()
|
||||
|
||||
if !gulu.File.IsDir(emojisDir) {
|
||||
os.MkdirAll(emojisDir, 0755)
|
||||
}
|
||||
|
||||
if err := emojisWatcher.Add(emojisDir); err != nil {
|
||||
logging.LogErrorf("add emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-emojisWatcher.Event:
|
||||
|
|
@ -61,13 +74,6 @@ func watchEmojis() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := emojisWatcher.Add(emojisDir); err != nil {
|
||||
logging.LogErrorf("add emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
//logging.LogInfof("added file watcher [%s]", emojisDir)
|
||||
if err := emojisWatcher.Start(10 * time.Second); err != nil {
|
||||
logging.LogErrorf("start emojis watcher for folder [%s] failed: %s", emojisDir, err)
|
||||
return
|
||||
|
|
@ -77,5 +83,6 @@ func watchEmojis() {
|
|||
func CloseWatchEmojis() {
|
||||
if nil != emojisWatcher {
|
||||
emojisWatcher.Close()
|
||||
emojisWatcher = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
161
kernel/model/themes_watcher.go
Normal file
161
kernel/model/themes_watcher.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// SiYuan - Refactor your thinking
|
||||
// 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 !darwin
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
var themesWatcher *fsnotify.Watcher
|
||||
|
||||
func WatchThemes() {
|
||||
if !isFileWatcherAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
go watchThemes()
|
||||
}
|
||||
|
||||
func watchThemes() {
|
||||
CloseWatchThemes()
|
||||
themesDir := util.ThemesPath
|
||||
|
||||
var err error
|
||||
themesWatcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logging.LogErrorf("add themes watcher for folder [%s] failed: %s", themesDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !gulu.File.IsDir(themesDir) {
|
||||
os.MkdirAll(themesDir, 0755)
|
||||
}
|
||||
|
||||
if err = themesWatcher.Add(themesDir); err != nil {
|
||||
logging.LogErrorf("add themes root watcher for folder [%s] failed: %s", themesDir, err)
|
||||
CloseWatchThemes()
|
||||
return
|
||||
}
|
||||
|
||||
// 为每个子目录添加监听,以便收到 theme.css 的变更
|
||||
addThemesSubdirs(themesWatcher, themesDir)
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
var (
|
||||
timer *time.Timer
|
||||
lastEvent fsnotify.Event
|
||||
)
|
||||
timer = time.NewTimer(100 * time.Millisecond)
|
||||
<-timer.C // timer should be expired at first
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-themesWatcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 新目录创建时加入监听
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
if isThemesDirectSubdir(event.Name) {
|
||||
if addErr := themesWatcher.Add(event.Name); addErr != nil {
|
||||
logging.LogWarnf("add themes watcher for new folder [%s] failed: %s", event.Name, addErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastEvent = event
|
||||
timer.Reset(time.Millisecond * 100)
|
||||
case err, ok := <-themesWatcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
logging.LogErrorf("watch themes failed: %s", err)
|
||||
case <-timer.C:
|
||||
handleThemesEvent(lastEvent)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// addThemesSubdirs 为 themes 下每个子目录添加监听
|
||||
func addThemesSubdirs(w *fsnotify.Watcher, themesDir string) {
|
||||
entries, err := os.ReadDir(themesDir)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read themes folder failed: %s", err)
|
||||
return
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !util.IsDirRegularOrSymlink(e) {
|
||||
continue
|
||||
}
|
||||
subdir := filepath.Join(themesDir, e.Name())
|
||||
if addErr := w.Add(subdir); addErr != nil {
|
||||
logging.LogWarnf("add themes watcher for folder [%s] failed: %s", subdir, addErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isThemesDirectSubdir 判断 path 是否为 themes 下的直接子目录
|
||||
func isThemesDirectSubdir(path string) bool {
|
||||
if !gulu.File.IsDir(path) {
|
||||
return false
|
||||
}
|
||||
rel, err := filepath.Rel(util.ThemesPath, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if filepath.Base(path) != rel {
|
||||
return false
|
||||
}
|
||||
entries, err := os.ReadDir(util.ThemesPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
name := filepath.Base(path)
|
||||
for _, e := range entries {
|
||||
if e.Name() == name {
|
||||
return util.IsDirRegularOrSymlink(e)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleThemesEvent(event fsnotify.Event) {
|
||||
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||
return
|
||||
}
|
||||
broadcastRefreshThemeIfCurrent(event.Name)
|
||||
}
|
||||
|
||||
func CloseWatchThemes() {
|
||||
if nil != themesWatcher {
|
||||
themesWatcher.Close()
|
||||
themesWatcher = nil
|
||||
}
|
||||
}
|
||||
156
kernel/model/themes_watcher_darwin.go
Normal file
156
kernel/model/themes_watcher_darwin.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// SiYuan - Refactor your thinking
|
||||
// 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 darwin
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/radovskyb/watcher"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
var themesWatcher *watcher.Watcher
|
||||
|
||||
func WatchThemes() {
|
||||
if !isFileWatcherAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
go watchThemes()
|
||||
}
|
||||
|
||||
func watchThemes() {
|
||||
CloseWatchThemes()
|
||||
themesDir := util.ThemesPath
|
||||
|
||||
themesWatcher = watcher.New()
|
||||
|
||||
if !gulu.File.IsDir(themesDir) {
|
||||
os.MkdirAll(themesDir, 0755)
|
||||
}
|
||||
|
||||
if err := themesWatcher.Add(themesDir); err != nil {
|
||||
logging.LogErrorf("add themes watcher for folder [%s] failed: %s", themesDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 为每个子目录添加监听,以便收到 theme.css 的变更
|
||||
addThemesSubdirs(themesWatcher, themesDir)
|
||||
|
||||
go func() {
|
||||
defer logging.Recover()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-themesWatcher.Event:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 新目录创建时加入监听
|
||||
if watcher.Create == event.Op {
|
||||
if isThemesDirectSubdir(event.Path) {
|
||||
if addErr := themesWatcher.Add(event.Path); addErr != nil {
|
||||
logging.LogWarnf("add themes watcher for new folder [%s] failed: %s", event.Path, addErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleThemesEvent(event)
|
||||
case err, ok := <-themesWatcher.Error:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
logging.LogErrorf("watch themes failed: %s", err)
|
||||
case <-themesWatcher.Closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := themesWatcher.Start(10 * time.Second); err != nil {
|
||||
logging.LogErrorf("start themes watcher for folder [%s] failed: %s", themesDir, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// addThemesSubdirs 为 themes 下每个子目录添加监听
|
||||
func addThemesSubdirs(w *watcher.Watcher, themesDir string) {
|
||||
entries, err := os.ReadDir(themesDir)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read themes folder failed: %s", err)
|
||||
return
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !util.IsDirRegularOrSymlink(e) {
|
||||
continue
|
||||
}
|
||||
subdir := filepath.Join(themesDir, e.Name())
|
||||
if addErr := w.Add(subdir); addErr != nil {
|
||||
logging.LogWarnf("add themes watcher for folder [%s] failed: %s", subdir, addErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isThemesDirectSubdir 判断 path 是否为 themes 下的直接子目录
|
||||
func isThemesDirectSubdir(path string) bool {
|
||||
if !gulu.File.IsDir(path) {
|
||||
return false
|
||||
}
|
||||
rel, err := filepath.Rel(util.ThemesPath, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if filepath.Base(path) != rel {
|
||||
return false
|
||||
}
|
||||
entries, err := os.ReadDir(util.ThemesPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
name := filepath.Base(path)
|
||||
for _, e := range entries {
|
||||
if e.Name() == name {
|
||||
return util.IsDirRegularOrSymlink(e)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleThemesEvent(event watcher.Event) {
|
||||
if watcher.Write != event.Op {
|
||||
return
|
||||
}
|
||||
if !strings.HasSuffix(event.Path, "theme.css") {
|
||||
return
|
||||
}
|
||||
broadcastRefreshThemeIfCurrent(event.Path)
|
||||
}
|
||||
|
||||
func CloseWatchThemes() {
|
||||
if nil != themesWatcher {
|
||||
themesWatcher.Close()
|
||||
themesWatcher = nil
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue