Files
claude-plugin-runescape/scripts/rsw/internal/prices/mapping.go
2026-03-05 22:45:55 -06:00

124 lines
2.8 KiB
Go

package prices
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
const cacheTTL = 24 * time.Hour
// MappingCache provides a local cache of the item ID ↔ name mapping.
type MappingCache struct {
client *Client
items []MappingItem
byID map[int]*MappingItem
byName map[string]*MappingItem
cacheDir string
game string
}
// NewMappingCache creates a mapping cache backed by the given client.
// The game parameter scopes the cache file (e.g. "osrs" or "rs3").
func NewMappingCache(client *Client, game string) *MappingCache {
home, _ := os.UserHomeDir()
return &MappingCache{
client: client,
byID: make(map[int]*MappingItem),
byName: make(map[string]*MappingItem),
cacheDir: filepath.Join(home, ".rsw", "cache"),
game: game,
}
}
// Load populates the cache, reading from disk if fresh or fetching from API.
func (mc *MappingCache) Load() error {
// Try disk cache first
if mc.loadFromDisk() {
return nil
}
items, err := mc.client.GetMapping()
if err != nil {
return fmt.Errorf("fetching mapping: %w", err)
}
mc.items = items
mc.index()
mc.saveToDisk()
return nil
}
// LookupByName finds an item by exact name (case-insensitive).
func (mc *MappingCache) LookupByName(name string) *MappingItem {
return mc.byName[strings.ToLower(name)]
}
// SearchByName finds items whose name contains the query (case-insensitive).
func (mc *MappingCache) SearchByName(query string) []*MappingItem {
query = strings.ToLower(query)
var results []*MappingItem
for i := range mc.items {
if strings.Contains(strings.ToLower(mc.items[i].Name), query) {
results = append(results, &mc.items[i])
}
}
return results
}
// LookupByID finds an item by its ID.
func (mc *MappingCache) LookupByID(id int) *MappingItem {
return mc.byID[id]
}
func (mc *MappingCache) index() {
mc.byID = make(map[int]*MappingItem, len(mc.items))
mc.byName = make(map[string]*MappingItem, len(mc.items))
for i := range mc.items {
mc.byID[mc.items[i].ID] = &mc.items[i]
mc.byName[strings.ToLower(mc.items[i].Name)] = &mc.items[i]
}
}
func (mc *MappingCache) cachePath() string {
filename := mc.game + "_mapping.json"
return filepath.Join(mc.cacheDir, filename)
}
func (mc *MappingCache) loadFromDisk() bool {
path := mc.cachePath()
info, err := os.Stat(path)
if err != nil {
return false
}
if time.Since(info.ModTime()) > cacheTTL {
return false
}
data, err := os.ReadFile(path)
if err != nil {
return false
}
var items []MappingItem
if err := json.Unmarshal(data, &items); err != nil {
return false
}
mc.items = items
mc.index()
return true
}
func (mc *MappingCache) saveToDisk() {
_ = os.MkdirAll(mc.cacheDir, 0755)
data, err := json.Marshal(mc.items)
if err != nil {
return
}
_ = os.WriteFile(mc.cachePath(), data, 0644)
}