diff --git a/bytechan_writer.go b/bytechan_writer.go new file mode 100644 index 0000000..5c34760 --- /dev/null +++ b/bytechan_writer.go @@ -0,0 +1,20 @@ +package main + +import "io" + +func NewWriterFromByteChan(ch chan byte) *ByteChanWriter { + return &ByteChanWriter{ch} +} + +type ByteChanWriter struct { + ch chan byte +} + +var _ io.Writer = &ByteChanWriter{} + +func (w *ByteChanWriter) Write(p []byte) (n int, err error) { + for _, b := range p { + w.ch <- b + } + return len(p), nil +} diff --git a/envvars.go b/envvars.go index 82efaf8..b44eb81 100644 --- a/envvars.go +++ b/envvars.go @@ -13,6 +13,8 @@ var ( ThreadCount = envvars.Int("THREADS", runtime.NumCPU()) + DeleteLowPriorityFiles = envvars.Bool("DELETE_LOW_PRIORITY_FILES", false) + Uid = envvars.Object("UID", 1000, func(s string) (int, error) { if uid, err := strconv.Atoi(s); err == nil { return uid, nil diff --git a/go.mod b/go.mod index cb218de..4f54a5c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( git.milar.in/milarin/envvars/v2 v2.0.0 git.milar.in/nyaanime/logic v0.0.0-20230114105336-6bfb7dce349f git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 - git.milar.in/nyaanime/parsers v0.0.0-20230113101942-2c9bc6925201 + git.milar.in/nyaanime/parsers v0.0.0-20230115135225-d80026a240a2 github.com/fatih/color v1.13.0 github.com/fsnotify/fsnotify v1.5.4 ) diff --git a/go.sum b/go.sum index eb54dce..5c73570 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 h1:mXcEA47FQQzeSD git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3/go.mod h1:kPWLDvFrhc1Uf77gxsBOxNeJ5JTVF2HhVs1IdVcw0tg= git.milar.in/nyaanime/parsers v0.0.0-20230113101942-2c9bc6925201 h1:glTG4IeuIvD4mVwJyCF5SYMawCRcZZ01pz4AUyWTEP8= git.milar.in/nyaanime/parsers v0.0.0-20230113101942-2c9bc6925201/go.mod h1:GG4vtUIfxopZc/+Y8OAa//vWJw/m6aeoGN7fw6SLiEM= +git.milar.in/nyaanime/parsers v0.0.0-20230115135225-d80026a240a2 h1:Q95JBR9mXENAjRhvzPAsFjPfxY0ljUiLVlhfAO4q6UY= +git.milar.in/nyaanime/parsers v0.0.0-20230115135225-d80026a240a2/go.mod h1:GG4vtUIfxopZc/+Y8OAa//vWJw/m6aeoGN7fw6SLiEM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= diff --git a/handle_file.go b/handle_file.go index 4eaa23e..9482f6b 100644 --- a/handle_file.go +++ b/handle_file.go @@ -2,10 +2,10 @@ package main import ( "errors" + "fmt" "io" "os" "path/filepath" - "strings" "time" "git.milar.in/nyaanime/logic" @@ -14,64 +14,80 @@ import ( "github.com/fatih/color" ) -func HandleFile(path string) (b *strings.Builder) { - b = &strings.Builder{} - b.WriteString(color.MagentaString("%s file found: %s\n", time.Now().Format("2006-01-02 15:04:05"), path)) +func HandleFile(path string) <-chan byte { + out := make(chan byte, 1024) + defer close(out) + w := NewWriterFromByteChan(out) + + fmt.Fprint(w, color.MagentaString("%s file found: %s\n", time.Now().Format("2006-01-02 15:04:05"), path)) for _, parser := range parsers.Parsers { parsedFile, ok := parser.FileParser(&parser, path) if !ok { - b.WriteString(color.YellowString("\tnot parsable with parser '%s'\n", parser.Identity)) + fmt.Fprint(w, color.YellowString("\tnot parsable with parser '%s'\n", parser.Identity)) continue } - b.WriteString(color.GreenString("\tparsable with parser '%s'\n", parser.Identity)) + fmt.Fprint(w, color.GreenString("\tparsable with parser '%s'\n", parser.Identity)) anime, err := logic.SearchAnimeByTitle(parsedFile.OriginalAnimeTitle) if err != nil { - b.WriteString(color.RedString("\tanime not found: '%s'\n", parsedFile.OriginalAnimeTitle)) + fmt.Fprint(w, color.RedString("\tanime not found: '%s'\n", parsedFile.OriginalAnimeTitle)) continue } parsedFile.Anime = anime - HandleParsedFile(b, parsedFile) - return + HandleParsedFile(w, parsedFile) + break } - return + return out } -func HandleParsedFile(b *strings.Builder, parsedFile *model.ParsedFile) { +func HandleParsedFile(w io.Writer, parsedFile *model.ParsedFile) { newFilePrio := logic.NewFilePriority(parsedFile) oldFilePrio, animeEpNotExistLocally := logic.GetAnimeEpProps(parsedFile.AnimeEpisode()) + // debug output if animeEpNotExistLocally { - b.WriteString(color.YellowString("\tfile exists locally\n")) - b.WriteString(color.YellowString("\t local file: %s\n", FilePrio2Str(oldFilePrio))) - b.WriteString(color.YellowString("\t new file: %s\n", FilePrio2Str(newFilePrio))) + fmt.Fprint(w, color.YellowString("\tfile exists locally\n")) + fmt.Fprint(w, color.YellowString("\t local file: %s\n", FilePrio2Str(oldFilePrio))) + fmt.Fprint(w, color.YellowString("\t new file: %s\n", FilePrio2Str(newFilePrio))) if newFilePrio.Priority > oldFilePrio.Priority { - b.WriteString(color.GreenString("\t overwrite local file\n")) + fmt.Fprint(w, color.GreenString("\t overwrite local file\n")) } else { - b.WriteString(color.YellowString("\t ignore new file\n")) + fmt.Fprint(w, color.YellowString("\t ignore new file\n")) } } + // delete files with lower priority from DownloadPath + if DeleteLowPriorityFiles && animeEpNotExistLocally && oldFilePrio.Priority >= newFilePrio.Priority { + fmt.Fprint(w, color.YellowString("\tdelete file with lower priority '%s'", parsedFile.File)) + err := os.Remove(parsedFile.File) + if err != nil { + fmt.Fprint(w, color.RedString(" failed: '%s'\n", err.Error())) + } else { + fmt.Fprint(w, color.GreenString(" done\n")) + } + return + } + if !animeEpNotExistLocally || newFilePrio.Priority > oldFilePrio.Priority { // TODO remove old file when overwriting existing episode (might have other file extension) - if err := OrganizeAnimeEpisode(b, parsedFile); err != nil { - b.WriteString(color.RedString("\terror: %s\n", err.Error())) + if err := OrganizeAnimeEpisode(w, parsedFile); err != nil { + fmt.Fprint(w, color.RedString("\terror: %s\n", err.Error())) } } } -func OrganizeAnimeEpisode(b *strings.Builder, parsedFile *model.ParsedFile) error { +func OrganizeAnimeEpisode(w io.Writer, parsedFile *model.ParsedFile) error { start := time.Now() oldFile := filepath.Join(DownloadPath, parsedFile.File) newFile := logic.GetAnimeEpFilepath(parsedFile.AnimeEpisode(), filepath.Ext(parsedFile.File)) lockFile := logic.GetAnimeEpFilepath(parsedFile.AnimeEpisode(), "lock") - b.WriteString(color.BlueString("\tmove file\n\t from: '%s'\n\t to: '%s'\n", oldFile, newFile)) + fmt.Fprint(w, color.BlueString("\tmove file\n\t from: '%s'\n\t to: '%s'\n", oldFile, newFile)) if err := os.MkdirAll(filepath.Dir(newFile), os.ModePerm); err != nil { return err @@ -114,7 +130,7 @@ func OrganizeAnimeEpisode(b *strings.Builder, parsedFile *model.ParsedFile) erro return err } - b.WriteString(color.BlueString("\t done (copying %s took %s)\n", FormatBytes(written), time.Since(start).Truncate(100*time.Millisecond))) + fmt.Fprint(w, color.BlueString("\t done (copied %s in %s)\n", FormatBytes(written), time.Since(start).Truncate(100*time.Millisecond))) return nil } diff --git a/main.go b/main.go index 3bee774..83bd4a8 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,8 @@ package main import ( - "fmt" + "os" "os/exec" - "strings" "git.milar.in/milarin/channel" "git.milar.in/nyaanime/logic" @@ -29,11 +28,7 @@ func main() { } outputChan := channel.MapWithRunner(fsChan, InitializeRunner(), HandleFile) - channel.Each(outputChan, PrintStringBuilder) -} - -func PrintStringBuilder(b *strings.Builder) { - fmt.Println(b.String()) + channel.Each(outputChan, PrintByteChanFunc(os.Stdout)) } func InitializeRunner() channel.Runner { diff --git a/organizer b/organizer new file mode 100755 index 0000000..178cc94 Binary files /dev/null and b/organizer differ diff --git a/utils.go b/utils.go index 28a6f87..3d3df28 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io" "git.milar.in/milarin/gmath" ) @@ -21,3 +22,22 @@ func FormatBytes[T gmath.Integer](bytes T) string { return fmt.Sprintf("%.02fB", value) } } + +func PrintByteChan(w io.Writer, ch <-chan byte) error { + for b := range ch { + if _, err := w.Write([]byte{b}); err != nil { + return err + } + } + + fmt.Fprintln(w) + return nil +} + +func PrintByteChanFunc(w io.Writer) func(ch <-chan byte) { + return func(ch <-chan byte) { + if err := PrintByteChan(w, ch); err != nil { + panic(err) // TODO error handling + } + } +}