Mappings between local playlist folders and Kreativ-Tonies are now
expressed purely through the filesystem instead of a YAML config:
<root>/<household_id>/<freier_name> [id=<tonie_id>]/*.mp3
- internal/library: discovers tonie folders from this structure
(Discover/Filter), and scaffolds it automatically from the
TonieCloud account (Scaffold), leaving already-existing folders
(matched by tonie id) untouched
- internal/syncer: decoupled from the removed config package via a
plain Target{TonieID, Folder, Prune} struct
- cmd/toni-sync: new `init --root PATH` command to scaffold the
library; `sync [FILTER] --root PATH` now discovers targets from the
folder tree instead of a config file; removed the `config` command
group entirely
- Unit tests for folder discovery, filtering, and scaffolding
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
99 lines
2.7 KiB
Go
99 lines
2.7 KiB
Go
package library
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/arnef/toni-sync/internal/tonieapi"
|
|
)
|
|
|
|
// ScaffoldClient is the subset of tonieapi.Client needed to scaffold a library folder tree.
|
|
type ScaffoldClient interface {
|
|
GetHouseholds() ([]tonieapi.Household, error)
|
|
GetCreativeTonies(householdID string) ([]tonieapi.CreativeTonie, error)
|
|
}
|
|
|
|
// ScaffoldResult reports what Scaffold did for a single tonie.
|
|
type ScaffoldResult struct {
|
|
HouseholdID string
|
|
TonieID string
|
|
Name string
|
|
Folder string
|
|
Created bool // false if a matching folder already existed and was left untouched
|
|
}
|
|
|
|
// Scaffold creates the "<root>/<household_id>/<name> [id=<tonie_id>]" folder
|
|
// structure for every household and Kreativ-Tonie on the account. Existing
|
|
// tonie folders (matched by id, regardless of the current display name) are
|
|
// left untouched so local audio files are never affected.
|
|
func Scaffold(client ScaffoldClient, root string) ([]ScaffoldResult, error) {
|
|
households, err := client.GetHouseholds()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var results []ScaffoldResult
|
|
for _, h := range households {
|
|
householdPath := filepath.Join(root, h.ID)
|
|
if err := os.MkdirAll(householdPath, 0o755); err != nil {
|
|
return nil, fmt.Errorf("creating household folder %s: %w", householdPath, err)
|
|
}
|
|
|
|
existingByID, err := existingTonieFoldersByID(householdPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tonies, err := client.GetCreativeTonies(h.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, t := range tonies {
|
|
if existingDir, ok := existingByID[t.ID]; ok {
|
|
results = append(results, ScaffoldResult{
|
|
HouseholdID: h.ID,
|
|
TonieID: t.ID,
|
|
Name: t.Name,
|
|
Folder: filepath.Join(householdPath, existingDir),
|
|
Created: false,
|
|
})
|
|
continue
|
|
}
|
|
|
|
dirName := DirName(t.Name, t.ID)
|
|
tonieFolder := filepath.Join(householdPath, dirName)
|
|
if err := os.MkdirAll(tonieFolder, 0o755); err != nil {
|
|
return nil, fmt.Errorf("creating tonie folder %s: %w", tonieFolder, err)
|
|
}
|
|
results = append(results, ScaffoldResult{
|
|
HouseholdID: h.ID,
|
|
TonieID: t.ID,
|
|
Name: t.Name,
|
|
Folder: tonieFolder,
|
|
Created: true,
|
|
})
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// existingTonieFoldersByID maps tonie id -> existing folder name within a household folder.
|
|
func existingTonieFoldersByID(householdPath string) (map[string]string, error) {
|
|
entries, err := os.ReadDir(householdPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading household folder %s: %w", householdPath, err)
|
|
}
|
|
byID := make(map[string]string)
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
if _, id, ok := ParseTonieDir(e.Name()); ok {
|
|
byID[id] = e.Name()
|
|
}
|
|
}
|
|
return byID, nil
|
|
}
|