mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
refactor: unify json load/saving with jsonstore
This commit is contained in:
parent
7461344004
commit
4a65de99a8
6 changed files with 38 additions and 88 deletions
|
@ -7,13 +7,11 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal"
|
"github.com/yusing/go-proxy/internal"
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/favicon"
|
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/query"
|
"github.com/yusing/go-proxy/internal/api/v1/query"
|
||||||
"github.com/yusing/go-proxy/internal/auth"
|
"github.com/yusing/go-proxy/internal/auth"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/logging/memlogger"
|
"github.com/yusing/go-proxy/internal/logging/memlogger"
|
||||||
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
|
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
|
||||||
|
@ -80,8 +78,6 @@ func main() {
|
||||||
logging.Trace().Msg("trace enabled")
|
logging.Trace().Msg("trace enabled")
|
||||||
parallel(
|
parallel(
|
||||||
internal.InitIconListCache,
|
internal.InitIconListCache,
|
||||||
homepage.InitOverridesConfig,
|
|
||||||
favicon.InitIconCache,
|
|
||||||
systeminfo.Poller.Start,
|
systeminfo.Poller.Start,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@ package favicon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
"github.com/yusing/go-proxy/internal/jsonstore"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type cacheEntry struct {
|
type cacheEntry struct {
|
||||||
|
@ -18,27 +17,14 @@ type cacheEntry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache key can be absolute url or route name.
|
// cache key can be absolute url or route name.
|
||||||
var (
|
var iconCache = jsonstore.Store[*cacheEntry](common.NamespaceIconCache)
|
||||||
iconCache = make(map[string]*cacheEntry)
|
|
||||||
iconCacheMu sync.RWMutex
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
iconCacheTTL = 3 * 24 * time.Hour
|
iconCacheTTL = 3 * 24 * time.Hour
|
||||||
cleanUpInterval = time.Hour
|
cleanUpInterval = time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitIconCache() {
|
func init() {
|
||||||
iconCacheMu.Lock()
|
|
||||||
defer iconCacheMu.Unlock()
|
|
||||||
|
|
||||||
err := utils.LoadJSONIfExist(common.IconCachePath, &iconCache)
|
|
||||||
if err != nil {
|
|
||||||
logging.Error().Err(err).Msg("failed to load icon cache")
|
|
||||||
} else if len(iconCache) > 0 {
|
|
||||||
logging.Info().Int("count", len(iconCache)).Msg("icon cache loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
cleanupTicker := time.NewTicker(cleanUpInterval)
|
cleanupTicker := time.NewTicker(cleanUpInterval)
|
||||||
defer cleanupTicker.Stop()
|
defer cleanupTicker.Stop()
|
||||||
|
@ -51,29 +37,13 @@ func InitIconCache() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
task.OnProgramExit("save_favicon_cache", func() {
|
|
||||||
iconCacheMu.Lock()
|
|
||||||
defer iconCacheMu.Unlock()
|
|
||||||
|
|
||||||
if len(iconCache) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.SaveJSON(common.IconCachePath, &iconCache, 0o644); err != nil {
|
|
||||||
logging.Error().Err(err).Msg("failed to save icon cache")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pruneExpiredIconCache() {
|
func pruneExpiredIconCache() {
|
||||||
iconCacheMu.Lock()
|
|
||||||
defer iconCacheMu.Unlock()
|
|
||||||
|
|
||||||
nPruned := 0
|
nPruned := 0
|
||||||
for key, icon := range iconCache {
|
for key, icon := range iconCache.Range {
|
||||||
if icon.IsExpired() {
|
if icon.IsExpired() {
|
||||||
delete(iconCache, key)
|
iconCache.Delete(key)
|
||||||
nPruned++
|
nPruned++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,16 +57,11 @@ func routeKey(r route.HTTPRoute) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func PruneRouteIconCache(route route.HTTPRoute) {
|
func PruneRouteIconCache(route route.HTTPRoute) {
|
||||||
iconCacheMu.Lock()
|
iconCache.Delete(routeKey(route))
|
||||||
defer iconCacheMu.Unlock()
|
|
||||||
delete(iconCache, routeKey(route))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadIconCache(key string) *fetchResult {
|
func loadIconCache(key string) *fetchResult {
|
||||||
iconCacheMu.RLock()
|
icon, ok := iconCache.Load(key)
|
||||||
defer iconCacheMu.RUnlock()
|
|
||||||
|
|
||||||
icon, ok := iconCache[key]
|
|
||||||
if ok && icon != nil {
|
if ok && icon != nil {
|
||||||
logging.Debug().
|
logging.Debug().
|
||||||
Str("key", key).
|
Str("key", key).
|
||||||
|
@ -108,9 +73,7 @@ func loadIconCache(key string) *fetchResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
func storeIconCache(key string, icon []byte) {
|
func storeIconCache(key string, icon []byte) {
|
||||||
iconCacheMu.Lock()
|
iconCache.Store(key, &cacheEntry{Icon: icon, LastAccess: time.Now()})
|
||||||
defer iconCacheMu.Unlock()
|
|
||||||
iconCache[key] = &cacheEntry{Icon: icon, LastAccess: time.Now()}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *cacheEntry) IsExpired() bool {
|
func (e *cacheEntry) IsExpired() bool {
|
||||||
|
|
|
@ -10,13 +10,16 @@ const (
|
||||||
DotEnvPath = ".env"
|
DotEnvPath = ".env"
|
||||||
DotEnvExamplePath = ".env.example"
|
DotEnvExamplePath = ".env.example"
|
||||||
|
|
||||||
ConfigBasePath = "config"
|
ConfigBasePath = "config"
|
||||||
ConfigFileName = "config.yml"
|
ConfigFileName = "config.yml"
|
||||||
ConfigExampleFileName = "config.example.yml"
|
ConfigExampleFileName = "config.example.yml"
|
||||||
ConfigPath = ConfigBasePath + "/" + ConfigFileName
|
ConfigPath = ConfigBasePath + "/" + ConfigFileName
|
||||||
HomepageJSONConfigPath = ConfigBasePath + "/.homepage.json"
|
|
||||||
IconListCachePath = ConfigBasePath + "/.icon_list_cache.json"
|
IconListCachePath = ConfigBasePath + "/.icon_list_cache.json"
|
||||||
IconCachePath = ConfigBasePath + "/.icon_cache.json"
|
IconCachePath = ConfigBasePath + "/.icon_cache.json"
|
||||||
|
|
||||||
|
NamespaceHomepageOverrides = ".homepage"
|
||||||
|
NamespaceIconCache = ".icon_cache"
|
||||||
|
|
||||||
MiddlewareComposeBasePath = ConfigBasePath + "/middlewares"
|
MiddlewareComposeBasePath = ConfigBasePath + "/middlewares"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOverrideItem(t *testing.T) {
|
func TestOverrideItem(t *testing.T) {
|
||||||
InitOverridesConfig()
|
|
||||||
a := &Item{
|
a := &Item{
|
||||||
Alias: "foo",
|
Alias: "foo",
|
||||||
ItemConfig: &ItemConfig{
|
ItemConfig: &ItemConfig{
|
||||||
|
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/jsonstore"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OverrideConfig struct {
|
type OverrideConfig struct {
|
||||||
|
@ -17,34 +15,7 @@ type OverrideConfig struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var overrideConfigInstance = &OverrideConfig{
|
var overrideConfigInstance = jsonstore.Object[OverrideConfig](common.NamespaceHomepageOverrides)
|
||||||
ItemOverrides: make(map[string]*ItemConfig),
|
|
||||||
DisplayOrder: make(map[string]int),
|
|
||||||
CategoryOrder: make(map[string]int),
|
|
||||||
ItemVisibility: make(map[string]bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitOverridesConfig() {
|
|
||||||
overrideConfigInstance.mu.Lock()
|
|
||||||
defer overrideConfigInstance.mu.Unlock()
|
|
||||||
|
|
||||||
err := utils.LoadJSONIfExist(common.HomepageJSONConfigPath, overrideConfigInstance)
|
|
||||||
if err != nil {
|
|
||||||
logging.Error().Err(err).Msg("failed to load homepage overrides config")
|
|
||||||
} else if len(overrideConfigInstance.ItemOverrides) > 0 {
|
|
||||||
logging.Info().
|
|
||||||
Int("count", len(overrideConfigInstance.ItemOverrides)).
|
|
||||||
Msg("homepage overrides config loaded")
|
|
||||||
}
|
|
||||||
task.OnProgramExit("save_homepage_json_config", func() {
|
|
||||||
if len(overrideConfigInstance.ItemOverrides) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := utils.SaveJSON(common.HomepageJSONConfigPath, overrideConfigInstance, 0o644); err != nil {
|
|
||||||
logging.Error().Err(err).Msg("failed to save homepage overrides config")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetOverrideConfig() *OverrideConfig {
|
func GetOverrideConfig() *OverrideConfig {
|
||||||
return overrideConfigInstance
|
return overrideConfigInstance
|
||||||
|
|
|
@ -2,6 +2,7 @@ package jsonstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -46,6 +47,8 @@ func load() error {
|
||||||
for ns, store := range stores.m {
|
for ns, store := range stores.m {
|
||||||
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &store); err != nil {
|
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &store); err != nil {
|
||||||
errs.Add(err)
|
errs.Add(err)
|
||||||
|
} else {
|
||||||
|
logging.Info().Str("name", string(ns)).Msg("store loaded")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
|
@ -74,6 +77,21 @@ func Store[VT any](namespace namespace) Typed[VT] {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Object[VT any](namespace namespace) *VT {
|
||||||
|
stores.Lock()
|
||||||
|
defer stores.Unlock()
|
||||||
|
if s, ok := stores.m[namespace]; ok {
|
||||||
|
v, ok := s.(*VT)
|
||||||
|
if ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
|
||||||
|
}
|
||||||
|
v := new(VT)
|
||||||
|
stores.m[namespace] = v
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (s Typed[VT]) MarshalJSON() ([]byte, error) {
|
func (s Typed[VT]) MarshalJSON() ([]byte, error) {
|
||||||
tmp := make(map[string]VT, s.Size())
|
tmp := make(map[string]VT, s.Size())
|
||||||
for k, v := range s.Range {
|
for k, v := range s.Range {
|
||||||
|
|
Loading…
Add table
Reference in a new issue