mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
Refactor diff jobs into task-owned renderers with persistent cache support, root-confined walkers, and quieter Mach-O/Firmware output.
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"maps"
|
|
"sync"
|
|
)
|
|
|
|
// MemoryStore is the in-memory Store used for OTA/Directory diff modes, tests,
|
|
// and any run that does not need cross-invocation persistence. Values are gob
|
|
// encoded on Put and decoded on Get so the observable behavior matches
|
|
// SQLiteStore: Tasks always round-trip through the same serialization, and a
|
|
// MemoryStore cache hit cannot accidentally hand back a live struct that a
|
|
// SQLite hit would have decoded into a fresh allocation.
|
|
type MemoryStore struct {
|
|
mu sync.RWMutex
|
|
// rows is keyed by Scope, then by row key. The inner map is non-nil for
|
|
// any Scope that has ever been written to.
|
|
rows map[Scope]map[string][]byte
|
|
// complete records which Scopes have been marked complete by their
|
|
// owning Task.
|
|
complete map[Scope]bool
|
|
}
|
|
|
|
// NewMemoryStore returns an empty in-memory Store. The zero value is not
|
|
// usable; callers must go through this constructor so the internal maps are
|
|
// allocated up front.
|
|
func NewMemoryStore() *MemoryStore {
|
|
return &MemoryStore{
|
|
rows: make(map[Scope]map[string][]byte),
|
|
complete: make(map[Scope]bool),
|
|
}
|
|
}
|
|
|
|
// Put gob-encodes v and stores it under (scope, key), replacing any prior
|
|
// value.
|
|
func (s *MemoryStore) Put(scope Scope, key string, v any) error {
|
|
var buf bytes.Buffer
|
|
if err := gob.NewEncoder(&buf).Encode(v); err != nil {
|
|
return fmt.Errorf("memory store: encode %s/%s: %w", scope.Task, key, err)
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
bucket, ok := s.rows[scope]
|
|
if !ok {
|
|
bucket = make(map[string][]byte)
|
|
s.rows[scope] = bucket
|
|
}
|
|
// Copy the encoded bytes so callers may reuse the input buffer freely.
|
|
enc := make([]byte, buf.Len())
|
|
copy(enc, buf.Bytes())
|
|
bucket[key] = enc
|
|
return nil
|
|
}
|
|
|
|
// Get loads the value at (scope, key) into v. found is false when no row
|
|
// exists; err is non-nil only on decode failures.
|
|
func (s *MemoryStore) Get(scope Scope, key string, v any) (bool, error) {
|
|
s.mu.RLock()
|
|
bucket, ok := s.rows[scope]
|
|
if !ok {
|
|
s.mu.RUnlock()
|
|
return false, nil
|
|
}
|
|
enc, ok := bucket[key]
|
|
s.mu.RUnlock()
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
if err := gob.NewDecoder(bytes.NewReader(enc)).Decode(v); err != nil {
|
|
return true, fmt.Errorf("memory store: decode %s/%s: %w", scope.Task, key, err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Iter calls fn for every row in scope. Rows are visited under a snapshot
|
|
// taken with the read lock held, so fn may freely call back into the store.
|
|
func (s *MemoryStore) Iter(scope Scope, fn func(key string, decode func(v any) error) error) error {
|
|
s.mu.RLock()
|
|
bucket, ok := s.rows[scope]
|
|
if !ok {
|
|
s.mu.RUnlock()
|
|
return nil
|
|
}
|
|
// Snapshot keys + payload pointers so fn may mutate the store via other
|
|
// Scopes without deadlocking. Values are immutable byte slices.
|
|
snapshot := make(map[string][]byte, len(bucket))
|
|
maps.Copy(snapshot, bucket)
|
|
s.mu.RUnlock()
|
|
for k, enc := range snapshot {
|
|
decode := func(v any) error {
|
|
if err := gob.NewDecoder(bytes.NewReader(enc)).Decode(v); err != nil {
|
|
return fmt.Errorf("memory store: decode %s/%s: %w", scope.Task, k, err)
|
|
}
|
|
return nil
|
|
}
|
|
if err := fn(k, decode); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MarkComplete records that every required row for scope has been written.
|
|
func (s *MemoryStore) MarkComplete(scope Scope) error {
|
|
s.mu.Lock()
|
|
s.complete[scope] = true
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Complete reports whether MarkComplete was previously called for scope.
|
|
func (s *MemoryStore) Complete(scope Scope) (bool, error) {
|
|
s.mu.RLock()
|
|
done := s.complete[scope]
|
|
s.mu.RUnlock()
|
|
return done, nil
|
|
}
|
|
|
|
// Close is a no-op for MemoryStore.
|
|
func (s *MemoryStore) Close() error { return nil }
|