refactor: unify json load/saving with jsonstore

This commit is contained in:
yusing 2025-04-24 05:49:32 +08:00
parent 7461344004
commit 4a65de99a8
6 changed files with 38 additions and 88 deletions

View file

@ -7,13 +7,11 @@ import (
"sync"
"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/auth"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/config"
"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/memlogger"
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
@ -80,8 +78,6 @@ func main() {
logging.Trace().Msg("trace enabled")
parallel(
internal.InitIconListCache,
homepage.InitOverridesConfig,
favicon.InitIconCache,
systeminfo.Poller.Start,
)

View file

@ -2,14 +2,13 @@ package favicon
import (
"encoding/json"
"sync"
"time"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/jsonstore"
"github.com/yusing/go-proxy/internal/logging"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils"
)
type cacheEntry struct {
@ -18,27 +17,14 @@ type cacheEntry struct {
}
// cache key can be absolute url or route name.
var (
iconCache = make(map[string]*cacheEntry)
iconCacheMu sync.RWMutex
)
var iconCache = jsonstore.Store[*cacheEntry](common.NamespaceIconCache)
const (
iconCacheTTL = 3 * 24 * time.Hour
cleanUpInterval = time.Hour
)
func InitIconCache() {
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")
}
func init() {
go func() {
cleanupTicker := time.NewTicker(cleanUpInterval)
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() {
iconCacheMu.Lock()
defer iconCacheMu.Unlock()
nPruned := 0
for key, icon := range iconCache {
for key, icon := range iconCache.Range {
if icon.IsExpired() {
delete(iconCache, key)
iconCache.Delete(key)
nPruned++
}
}
@ -87,16 +57,11 @@ func routeKey(r route.HTTPRoute) string {
}
func PruneRouteIconCache(route route.HTTPRoute) {
iconCacheMu.Lock()
defer iconCacheMu.Unlock()
delete(iconCache, routeKey(route))
iconCache.Delete(routeKey(route))
}
func loadIconCache(key string) *fetchResult {
iconCacheMu.RLock()
defer iconCacheMu.RUnlock()
icon, ok := iconCache[key]
icon, ok := iconCache.Load(key)
if ok && icon != nil {
logging.Debug().
Str("key", key).
@ -108,9 +73,7 @@ func loadIconCache(key string) *fetchResult {
}
func storeIconCache(key string, icon []byte) {
iconCacheMu.Lock()
defer iconCacheMu.Unlock()
iconCache[key] = &cacheEntry{Icon: icon, LastAccess: time.Now()}
iconCache.Store(key, &cacheEntry{Icon: icon, LastAccess: time.Now()})
}
func (e *cacheEntry) IsExpired() bool {

View file

@ -10,13 +10,16 @@ const (
DotEnvPath = ".env"
DotEnvExamplePath = ".env.example"
ConfigBasePath = "config"
ConfigFileName = "config.yml"
ConfigExampleFileName = "config.example.yml"
ConfigPath = ConfigBasePath + "/" + ConfigFileName
HomepageJSONConfigPath = ConfigBasePath + "/.homepage.json"
IconListCachePath = ConfigBasePath + "/.icon_list_cache.json"
IconCachePath = ConfigBasePath + "/.icon_cache.json"
ConfigBasePath = "config"
ConfigFileName = "config.yml"
ConfigExampleFileName = "config.example.yml"
ConfigPath = ConfigBasePath + "/" + ConfigFileName
IconListCachePath = ConfigBasePath + "/.icon_list_cache.json"
IconCachePath = ConfigBasePath + "/.icon_cache.json"
NamespaceHomepageOverrides = ".homepage"
NamespaceIconCache = ".icon_cache"
MiddlewareComposeBasePath = ConfigBasePath + "/middlewares"

View file

@ -7,7 +7,6 @@ import (
)
func TestOverrideItem(t *testing.T) {
InitOverridesConfig()
a := &Item{
Alias: "foo",
ItemConfig: &ItemConfig{

View file

@ -4,9 +4,7 @@ import (
"sync"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/jsonstore"
)
type OverrideConfig struct {
@ -17,34 +15,7 @@ type OverrideConfig struct {
mu sync.RWMutex
}
var overrideConfigInstance = &OverrideConfig{
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")
}
})
}
var overrideConfigInstance = jsonstore.Object[OverrideConfig](common.NamespaceHomepageOverrides)
func GetOverrideConfig() *OverrideConfig {
return overrideConfigInstance

View file

@ -2,6 +2,7 @@ package jsonstore
import (
"encoding/json"
"fmt"
"path/filepath"
"sync"
@ -46,6 +47,8 @@ func load() error {
for ns, store := range stores.m {
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &store); err != nil {
errs.Add(err)
} else {
logging.Info().Str("name", string(ns)).Msg("store loaded")
}
}
return errs.Error()
@ -74,6 +77,21 @@ func Store[VT any](namespace namespace) Typed[VT] {
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) {
tmp := make(map[string]VT, s.Size())
for k, v := range s.Range {