package convids import ( "bufio" "cmp" "context" "errors" "fmt" "io" "os" "os/exec" fp "path/filepath" "regexp" "strings" "time" "codeberg.org/danjones000/ezcache" "gopkg.in/yaml.v3" ) func NewData(path string) (*Data, error) { //nolint:gosec // I don't care if this is user input f, err := os.Open(path) if err != nil { return nil, err } ydec := yaml.NewDecoder(f) var data Data err = ydec.Decode(&data) return &data, err } func ensureExtRe(c *Config) (err error) { if c.extRe != nil { return nil } c.extRe, err = regexp.Compile("(" + strings.Join(c.Extensions, "|") + ")$") return } type ShowWalker func(show *Show, path string) error type GroupPrinter func(name string, group Shows) var ErrNoFiles = errors.New("no files processed") var ErrSkipped = errors.New("skipped") type processInput struct { file os.DirEntry source string show *Show extRe *regexp.Regexp skips []string walk ShowWalker } func processFile(input processInput) (processed bool, processError, cbError error) { file := input.file if file.IsDir() { return } if !input.extRe.MatchString(file.Name()) { return } show := input.show var found bool found, processError = show.Match(file.Name()) if processError != nil || !found { return } path := fp.Join(input.source, file.Name()) for _, ext := range input.skips { if !strings.HasPrefix(ext, ".") { ext = "." + ext } skipper := path + ext _, statErr := os.Stat(skipper) if !errors.Is(statErr, os.ErrNotExist) { return processed, ErrSkipped, cbError } } cbError = input.walk(show, path) if cbError == nil { processed = true } return } func processSource(allFiles ezcache.Cache[string, []os.DirEntry], d *Data, s *Show, source string, stopOnError bool, cb ShowWalker) (count int, err error) { files, filesErr := allFiles.Get(source) if filesErr != nil { return 0, filesErr } for _, file := range files { pIn := processInput{file, source, s, d.Config.extRe, d.Config.Skip, cb} processed, processErr, cbErr := processFile(pIn) if processErr != nil { return 0, processErr } if cbErr != nil && stopOnError { return 0, cbErr } if errors.Is(processErr, ErrSkipped) || (!processed && processErr == nil && cbErr == nil) { continue } if cbErr == nil && processed { count++ } if cbErr != nil { err = cbErr } return } return } func WalkFiles(d *Data, stopOnError bool, gp GroupPrinter, cb ShowWalker) (err error) { err = ensureExtRe(d.Config) if err != nil { return } if cb == nil { cb = DryRun(os.Stdout) } count := 0 allFiles, cacheErr := ezcache.New(os.ReadDir, 5*time.Minute) if cacheErr != nil { return err } if err != nil { return } for s := range d.AllShows(gp) { if len(s.Sources) == 0 { s.Sources = []string{d.Config.Source} } for _, source := range s.Sources { var c int c, err = processSource(allFiles, d, s, source, stopOnError, cb) if err != nil { break } count += c } } if err == nil && count < 1 { fmt.Println("Found nothing") return ErrNoFiles } return } func DryRun(out io.Writer) ShowWalker { return func(s *Show, path string) error { _, err := fmt.Fprintf(out, "Saving %s to %s\n", path, s.Folder) return cmp.Or(err, ErrSkipped) } } func GetShow(ctx context.Context) ShowWalker { return GetShowWithIO(ctx, os.Stdin, os.Stdout, os.Stderr) } func GetShowWithIO(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) ShowWalker { return func(s *Show, path string) error { if !s.Url { return runGetShowsCmd(ctx, s.Folder, path, stdin, stdout, stderr) } orig := path //nolint:gosec // I don't care if this is user input readFile, err := os.Open(orig) if err != nil { return err } //nolint:errcheck // I don't care defer readFile.Close() fileScan := bufio.NewScanner(readFile) fileScan.Split(bufio.ScanLines) for fileScan.Scan() { path = fileScan.Text() err := runGetShowsCmd(ctx, s.Folder, path, stdin, stdout, stderr) if err != nil { return err } } if s.Backup != "" { err = os.Rename(orig, fp.Join(s.Backup, fp.Base(orig))) if err != nil { _, _ = fmt.Fprintf(stdout, "Moved %s to %s\n", orig, s.Backup) } } else { err = os.Remove(orig) } return err } } func runGetShowsCmd(ctx context.Context, folder, path string, stdin io.Reader, stdout, stderr io.Writer) error { //nolint:gosec // I don't care if this is using user input cmd := exec.CommandContext(ctx, "get-shows", folder, path) cmd.Stdin = stdin cmd.Stdout = stdout cmd.Stderr = stderr return cmd.Run() } func PrintGroupName(out io.Writer) GroupPrinter { return func(name string, group Shows) { _, _ = fmt.Fprintf(out, "Checking %s shows\n\n", name) } }