package mirror
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
)
type Direction string
const (
Pull Direction = "pull"
Push Direction = "push"
)
const remoteName = "mirror"
type Entry struct {
Repo string `json:"repo"`
RemoteURL string `json:"remote_url"`
Direction Direction `json:"direction"`
IntervalSeconds int64 `json:"interval_seconds"`
LastSync time.Time `json:"last_sync"`
}
func (e *Entry) key() string { return string(e.Direction) + ":" + e.Repo }
type Store struct {
path string
mu sync.Mutex
}
func NewStore(root string) *Store {
return &Store{path: filepath.Join(root, "mirrors.json")}
}
func (s *Store) load() ([]Entry, error) {
data, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
var entries []Entry
if err := json.Unmarshal(data, &entries); err != nil {
return nil, err
}
return entries, nil
}
func (s *Store) save(entries []Entry) error {
data, err := json.MarshalIndent(entries, "", " ")
if err != nil {
return err
}
tmp := s.path + ".tmp"
if err := os.WriteFile(tmp, data, 0o644); err != nil {
return err
}
return os.Rename(tmp, s.path)
}
func (s *Store) Add(e Entry) error {
s.mu.Lock()
defer s.mu.Unlock()
entries, err := s.load()
if err != nil {
return err
}
out := entries[:0]
for _, existing := range entries {
if existing.key() != e.key() {
out = append(out, existing)
}
}
out = append(out, e)
return s.save(out)
}
func (s *Store) All() ([]Entry, error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.load()
}
func (s *Store) touch(e Entry, when time.Time) error {
s.mu.Lock()
defer s.mu.Unlock()
entries, err := s.load()
if err != nil {
return err
}
found := false
for i := range entries {
if entries[i].key() == e.key() {
entries[i].LastSync = when
found = true
}
}
if !found {
return fmt.Errorf("mirror %s not found", e.key())
}
return s.save(entries)
}