159 lines
4.9 KiB
Go
159 lines
4.9 KiB
Go
package mining
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
// repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json")
|
|
// data, err := repo.Load()
|
|
type Repository[T any] interface {
|
|
// data, err := repo.Load() // loads "/home/alice/.config/lethean-desktop/miners.json" into MinersConfig
|
|
Load() (T, error)
|
|
|
|
// repo.Save(updatedConfig) persists the updated config back to "/home/alice/.config/lethean-desktop/miners.json"
|
|
Save(data T) error
|
|
|
|
// repo.Update(func(configuration *MinersConfig) error { configuration.Miners = append(configuration.Miners, entry); return nil })
|
|
Update(modifier func(*T) error) error
|
|
}
|
|
|
|
// repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig))
|
|
// data, err := repo.Load()
|
|
type FileRepository[T any] struct {
|
|
mutex sync.RWMutex
|
|
filePath string
|
|
defaultValueProvider func() T
|
|
}
|
|
|
|
// repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig), myOption)
|
|
type FileRepositoryOption[T any] func(*FileRepository[T])
|
|
|
|
// repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig))
|
|
func WithDefaults[T any](defaultsProvider func() T) FileRepositoryOption[T] {
|
|
return func(repo *FileRepository[T]) {
|
|
repo.defaultValueProvider = defaultsProvider
|
|
}
|
|
}
|
|
|
|
// repo := NewFileRepository[MinersConfig]("/home/alice/.config/lethean-desktop/miners.json", WithDefaults(defaultMinersConfig))
|
|
func NewFileRepository[T any](filePath string, options ...FileRepositoryOption[T]) *FileRepository[T] {
|
|
repo := &FileRepository[T]{
|
|
filePath: filePath,
|
|
}
|
|
for _, option := range options {
|
|
option(repo)
|
|
}
|
|
return repo
|
|
}
|
|
|
|
// data, err := repo.Load()
|
|
// if err != nil { return defaults, err }
|
|
func (repository *FileRepository[T]) Load() (T, error) {
|
|
repository.mutex.RLock()
|
|
defer repository.mutex.RUnlock()
|
|
|
|
var result T
|
|
|
|
data, err := os.ReadFile(repository.filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
if repository.defaultValueProvider != nil {
|
|
return repository.defaultValueProvider(), nil
|
|
}
|
|
return result, nil
|
|
}
|
|
return result, ErrInternal("failed to read file").WithCause(err)
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return result, ErrInternal("failed to unmarshal data").WithCause(err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// if err := repo.Save(updated); err != nil { return ErrInternal("save").WithCause(err) }
|
|
func (repository *FileRepository[T]) Save(data T) error {
|
|
repository.mutex.Lock()
|
|
defer repository.mutex.Unlock()
|
|
|
|
return repository.saveUnlocked(data)
|
|
}
|
|
|
|
// repository.saveUnlocked(updatedConfig) // used by Save() and Update() while the mutex is already held
|
|
func (repository *FileRepository[T]) saveUnlocked(data T) error {
|
|
dir := filepath.Dir(repository.filePath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return ErrInternal("failed to create directory").WithCause(err)
|
|
}
|
|
|
|
jsonData, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
return ErrInternal("failed to marshal data").WithCause(err)
|
|
}
|
|
|
|
return AtomicWriteFile(repository.filePath, jsonData, 0600)
|
|
}
|
|
|
|
// repo.Update(func(configuration *MinersConfig) error {
|
|
// configuration.Miners = append(configuration.Miners, entry)
|
|
// return nil
|
|
// })
|
|
func (repository *FileRepository[T]) Update(modifier func(*T) error) error {
|
|
repository.mutex.Lock()
|
|
defer repository.mutex.Unlock()
|
|
|
|
// os.ReadFile("/home/alice/.config/lethean-desktop/miners.json") loads the current config before applying the modifier.
|
|
var data T
|
|
fileData, err := os.ReadFile(repository.filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
if repository.defaultValueProvider != nil {
|
|
data = repository.defaultValueProvider()
|
|
}
|
|
} else {
|
|
return ErrInternal("failed to read file").WithCause(err)
|
|
}
|
|
} else {
|
|
if err := json.Unmarshal(fileData, &data); err != nil {
|
|
return ErrInternal("failed to unmarshal data").WithCause(err)
|
|
}
|
|
}
|
|
|
|
// modifier(&data) can append `MinerAutostartConfig{MinerType: "xmrig", Autostart: true}` before saving.
|
|
if err := modifier(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// repository.saveUnlocked(data) writes the updated JSON atomically to "/home/alice/.config/lethean-desktop/miners.json".
|
|
return repository.saveUnlocked(data)
|
|
}
|
|
|
|
// filePath := repo.Path() // filePath == "/home/alice/.config/lethean-desktop/miners.json"
|
|
func (repository *FileRepository[T]) Path() string {
|
|
return repository.filePath
|
|
}
|
|
|
|
// if !repo.Exists() { return defaults, nil }
|
|
func (repository *FileRepository[T]) Exists() bool {
|
|
repository.mutex.RLock()
|
|
defer repository.mutex.RUnlock()
|
|
|
|
_, err := os.Stat(repository.filePath)
|
|
return err == nil
|
|
}
|
|
|
|
// if err := repo.Delete(); err != nil { return err }
|
|
func (repository *FileRepository[T]) Delete() error {
|
|
repository.mutex.Lock()
|
|
defer repository.mutex.Unlock()
|
|
|
|
err := os.Remove(repository.filePath)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|