Rewrite toni-sync in Go
- Replace the Python implementation with a Go module compiling to a single static binary (no Python/pip runtime dependency) - internal/tonieapi: minimal, dependency-light HTTP client for the TonieCloud REST API (login, households, creative tonies, file upload via presigned S3 request, chapter add/sort/clear) - internal/config: YAML-based mapping of local playlist folders to Kreativ-Tonies, credentials never stored - internal/syncer: diff/apply logic (upload new tracks, prune removed chapters, reorder to match local file order), built against a TonieClient interface for testability - cmd/toni-sync: Cobra CLI with `tonies list`, `config add/list/remove`, `sync [NAME|--all] [--dry-run]` - Unit tests for config persistence and syncer plan/apply logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// Package config manages the local mapping between playlist folders and
|
||||
// Kreativ-Tonies. Credentials are never stored here.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Mapping maps a local folder (e.g. an exported Deezer playlist) to a Kreativ-Tonie.
|
||||
type Mapping struct {
|
||||
Name string `yaml:"name"`
|
||||
HouseholdID string `yaml:"household_id"`
|
||||
TonieID string `yaml:"tonie_id"`
|
||||
TonieName string `yaml:"tonie_name,omitempty"`
|
||||
Folder string `yaml:"folder"`
|
||||
PlaylistRef string `yaml:"playlist_ref,omitempty"`
|
||||
Prune bool `yaml:"prune"`
|
||||
}
|
||||
|
||||
// Config is the full toni-sync configuration.
|
||||
type Config struct {
|
||||
Mappings []Mapping `yaml:"mappings"`
|
||||
}
|
||||
|
||||
// Get returns the mapping with the given name, if present.
|
||||
func (c *Config) Get(name string) *Mapping {
|
||||
for i := range c.Mappings {
|
||||
if c.Mappings[i].Name == name {
|
||||
return &c.Mappings[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert adds a new mapping or replaces an existing one with the same name.
|
||||
func (c *Config) Upsert(m Mapping) {
|
||||
for i := range c.Mappings {
|
||||
if c.Mappings[i].Name == m.Name {
|
||||
c.Mappings[i] = m
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Mappings = append(c.Mappings, m)
|
||||
}
|
||||
|
||||
// Remove deletes the mapping with the given name. Returns false if it was not found.
|
||||
func (c *Config) Remove(name string) bool {
|
||||
for i := range c.Mappings {
|
||||
if c.Mappings[i].Name == name {
|
||||
c.Mappings = append(c.Mappings[:i], c.Mappings[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultPath returns the default config file location, honoring TONI_SYNC_CONFIG.
|
||||
func DefaultPath() string {
|
||||
if p := os.Getenv("TONI_SYNC_CONFIG"); p != "" {
|
||||
return p
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "."
|
||||
}
|
||||
return filepath.Join(home, ".config", "toni-sync", "config.yaml")
|
||||
}
|
||||
|
||||
// Load reads the config from path, or returns an empty Config if it doesn't exist yet.
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &Config{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("reading config %s: %w", path, err)
|
||||
}
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parsing config %s: %w", path, err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Save writes the config to path, creating parent directories as needed.
|
||||
func Save(cfg *Config, path string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return fmt.Errorf("creating config dir: %w", err)
|
||||
}
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return fmt.Errorf("writing config %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user