173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package nigiri
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Core JSON Operations
|
|
// ============================================================================
|
|
|
|
func (bs *BaseStore[T]) LoadFromJSON(filename string) error {
|
|
bs.mu.Lock()
|
|
defer bs.mu.Unlock()
|
|
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to read JSON: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
|
|
sliceType := reflect.SliceOf(reflect.PointerTo(bs.itemType))
|
|
slicePtr := reflect.New(sliceType)
|
|
|
|
if err := json.Unmarshal(data, slicePtr.Interface()); err != nil {
|
|
return fmt.Errorf("failed to unmarshal JSON: %w", err)
|
|
}
|
|
|
|
bs.items = make(map[int]*T)
|
|
bs.maxID = 0
|
|
|
|
for fieldName := range bs.uniqueIndices {
|
|
bs.uniqueIndices[fieldName] = make(map[any]int)
|
|
}
|
|
|
|
slice := slicePtr.Elem()
|
|
for i := 0; i < slice.Len(); i++ {
|
|
item := slice.Index(i).Interface().(*T)
|
|
|
|
itemValue := reflect.ValueOf(item).Elem()
|
|
idField := itemValue.FieldByName("ID")
|
|
if !idField.IsValid() {
|
|
return fmt.Errorf("item type must have an ID field")
|
|
}
|
|
|
|
id := int(idField.Int())
|
|
bs.items[id] = item
|
|
if id > bs.maxID {
|
|
bs.maxID = id
|
|
}
|
|
|
|
bs.updateUniqueIndices(id, item, true)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (bs *BaseStore[T]) SaveToJSON(filename string) error {
|
|
bs.mu.RLock()
|
|
defer bs.mu.RUnlock()
|
|
|
|
ids := make([]int, 0, len(bs.items))
|
|
for id := range bs.items {
|
|
ids = append(ids, id)
|
|
}
|
|
sort.Ints(ids)
|
|
|
|
items := make([]*T, 0, len(bs.items))
|
|
for _, id := range ids {
|
|
items = append(items, bs.items[id])
|
|
}
|
|
|
|
data, err := json.MarshalIndent(items, "", "\t")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal to JSON: %w", err)
|
|
}
|
|
|
|
tempFile := filename + ".tmp"
|
|
if err := os.WriteFile(tempFile, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write temp JSON: %w", err)
|
|
}
|
|
|
|
if err := os.Rename(tempFile, filename); err != nil {
|
|
os.Remove(tempFile)
|
|
return fmt.Errorf("failed to rename temp JSON: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// StoreManager Interface Implementation
|
|
// ============================================================================
|
|
|
|
func (bs *BaseStore[T]) LoadData(dataPath string) error {
|
|
if err := bs.LoadFromJSON(dataPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
fmt.Println("No existing data found, starting with empty store")
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to load from JSON: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Loaded %d items from %s\n", len(bs.items), dataPath)
|
|
bs.RebuildIndices()
|
|
return nil
|
|
}
|
|
|
|
func (bs *BaseStore[T]) SaveData(dataPath string) error {
|
|
dataDir := filepath.Dir(dataPath)
|
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create data directory: %w", err)
|
|
}
|
|
|
|
if err := bs.SaveToJSON(dataPath); err != nil {
|
|
return fmt.Errorf("failed to save to JSON: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Saved %d items to %s\n", len(bs.items), dataPath)
|
|
return nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Migration Support
|
|
// ============================================================================
|
|
|
|
func (bs *BaseStore[T]) ValidateAfterMigration(filename string) error {
|
|
tempStore := NewBaseStore[T]()
|
|
if err := tempStore.LoadFromJSON(filename); err != nil {
|
|
return fmt.Errorf("migration validation failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bs *BaseStore[T]) RestoreFromBackup(filename string) error {
|
|
backupPath := filename + ".backup"
|
|
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("backup file does not exist: %s", backupPath)
|
|
}
|
|
|
|
if err := os.Rename(backupPath, filename); err != nil {
|
|
return fmt.Errorf("failed to restore from backup: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Restored from backup: %s\n", backupPath)
|
|
return nil
|
|
}
|
|
|
|
func (bs *BaseStore[T]) CreateMigrationCheckpoint(filename string) error {
|
|
checkpointPath := filename + ".checkpoint"
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read file for checkpoint: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(checkpointPath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to create checkpoint: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Created checkpoint: %s\n", checkpointPath)
|
|
return nil
|
|
}
|