diff --git a/cmd/main.go b/cmd/main.go index 9306529..07ba910 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -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, ) diff --git a/internal/api/v1/favicon/cache.go b/internal/api/v1/favicon/cache.go index b6764a8..5391d53 100644 --- a/internal/api/v1/favicon/cache.go +++ b/internal/api/v1/favicon/cache.go @@ -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 { diff --git a/internal/common/constants.go b/internal/common/constants.go index 0cc7a4b..8baf813 100644 --- a/internal/common/constants.go +++ b/internal/common/constants.go @@ -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" diff --git a/internal/homepage/homepage_test.go b/internal/homepage/homepage_test.go index b7836de..7ea580c 100644 --- a/internal/homepage/homepage_test.go +++ b/internal/homepage/homepage_test.go @@ -7,7 +7,6 @@ import ( ) func TestOverrideItem(t *testing.T) { - InitOverridesConfig() a := &Item{ Alias: "foo", ItemConfig: &ItemConfig{ diff --git a/internal/homepage/override_config.go b/internal/homepage/override_config.go index 59e3c1d..fe0ca1f 100644 --- a/internal/homepage/override_config.go +++ b/internal/homepage/override_config.go @@ -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 diff --git a/internal/jsonstore/jsonstore.go b/internal/jsonstore/jsonstore.go index d5823e0..db08414 100644 --- a/internal/jsonstore/jsonstore.go +++ b/internal/jsonstore/jsonstore.go @@ -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 {