// Package themes installs archduino theme files into ~/.config/archduino/themes. package themes import ( "bytes" "context" "fmt" "io/fs" "os" "path/filepath" "gitlab.hive-brain.de/MDietermann/Archduino/internal/config" "gitlab.hive-brain.de/MDietermann/Archduino/internal/module" ) type Module struct{ cfg config.ThemesConfig } func New(cfg config.ThemesConfig) Module { return Module{cfg: cfg} } type filePair struct{ src, dst string } type state struct{ Missing []filePair } func (Module) Meta() module.Meta { return module.Meta{ID: "themes", Name: "Colorscheme themes", Description: "Install archduino themes."} } func destRoot() (string, error) { h, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(h, ".config", "archduino", "themes"), nil } func (m Module) Detect(ctx context.Context) (module.State, error) { dst, err := destRoot() if err != nil { return nil, err } var missing []filePair err = filepath.WalkDir(m.cfg.Source, func(p string, d fs.DirEntry, werr error) error { if werr != nil { return werr } if d.IsDir() { return nil } rel, _ := filepath.Rel(m.cfg.Source, p) target := filepath.Join(dst, rel) a, errA := os.ReadFile(p) if errA != nil { return errA } b, errB := os.ReadFile(target) if errB != nil || !bytes.Equal(a, b) { missing = append(missing, filePair{src: p, dst: target}) } return nil }) if err != nil { if os.IsNotExist(err) { return state{}, nil } return nil, err } return state{Missing: missing}, nil } func (m Module) Plan(ctx context.Context, cur module.State) (module.Plan, error) { s, _ := cur.(state) if len(s.Missing) == 0 { return module.Plan{}, nil } pairs := append([]filePair{}, s.Missing...) return module.Plan{Actions: []module.Action{{ Description: fmt.Sprintf("copy %d theme files", len(pairs)), Kind: module.ActionFileWrite, Exec: func(ctx context.Context) error { for _, fp := range pairs { if err := os.MkdirAll(filepath.Dir(fp.dst), 0o755); err != nil { return err } data, err := os.ReadFile(fp.src) if err != nil { return err } if err := os.WriteFile(fp.dst, data, 0o644); err != nil { return err } } return nil }, }}}, nil } func (m Module) Apply(ctx context.Context, p module.Plan, r module.Reporter) error { for i, a := range p.Actions { r.Progress(m.Meta().ID, i+1, len(p.Actions), a.Description) if err := a.Exec(ctx); err != nil { return err } } return nil } func (m Module) Verify(ctx context.Context) error { st, err := m.Detect(ctx) if err != nil { return err } if miss := st.(state).Missing; len(miss) > 0 { return fmt.Errorf("themes still missing: %d files", len(miss)) } return nil }