230 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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"
 | |
| )
 | |
| 
 | |
| 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")
 | |
| var ErrMissingConfig = errors.New("missing config")
 | |
| var ErrMissingShows = errors.New("missing shows")
 | |
| 
 | |
| 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)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if data.Config == nil {
 | |
| 		return nil, ErrMissingConfig
 | |
| 	}
 | |
| 	if data.Shows == nil || len(*(data.Shows)) == 0 {
 | |
| 		return nil, ErrMissingShows
 | |
| 	}
 | |
| 	return &data, nil
 | |
| }
 | |
| 
 | |
| func ensureExtRe(c *Config) (err error) {
 | |
| 	if c.extRe != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	c.extRe, err = regexp.Compile("(" + strings.Join(c.Extensions, "|") + ")$")
 | |
| 	return
 | |
| }
 | |
| 
 | |
| 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)
 | |
| 	}
 | |
| }
 |