commit 523510946d38ce6a25a7f60729e75565f950903e Author: milarin Date: Fri Jan 13 19:23:36 2023 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/envvars.go b/envvars.go new file mode 100644 index 0000000..ddb8e24 --- /dev/null +++ b/envvars.go @@ -0,0 +1,38 @@ +package main + +import ( + "os/user" + "strconv" + + "git.milar.in/milarin/envvars/v2" +) + +var ( + DownloadPath = envvars.String("DOWNLOAD_PATH", "") + + Uid = envvars.Object("UID", 1000, func(s string) (int, error) { + if uid, err := strconv.Atoi(s); err == nil { + return uid, nil + } + + usr, err := user.Lookup(s) + if err != nil { + return 0, err + } + + return strconv.Atoi(usr.Uid) + }) + + Gid = envvars.Object("GID", 1000, func(s string) (int, error) { + if gid, err := strconv.Atoi(s); err == nil { + return gid, nil + } + + grp, err := user.LookupGroup(s) + if err != nil { + return 0, err + } + + return strconv.Atoi(grp.Gid) + }) +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42aea90 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module git.milar.in/nyaanime/organizer + +go 1.19 + +require ( + git.milar.in/milarin/adverr v1.1.0 + git.milar.in/milarin/envvars/v2 v2.0.0 + git.milar.in/nyaanime/logic v0.0.0-20230113102709-a719289ef360 + git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 + git.milar.in/nyaanime/parsers v0.0.0-20230113101942-2c9bc6925201 + github.com/fsnotify/fsnotify v1.5.4 +) + +require ( + git.milar.in/milarin/anilist v1.5.1 // indirect + git.milar.in/milarin/channel v0.0.14 // indirect + git.milar.in/milarin/gmath v0.0.3 // indirect + git.milar.in/milarin/slices v0.0.6 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + gopkg.in/vansante/go-ffprobe.v2 v2.1.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cbd2ae6 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0= +git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E= +git.milar.in/milarin/anilist v1.5.1 h1:gW08WaAvXxC5/+P1QCjyfa5YtaiY9XvF5x/8G4orT6I= +git.milar.in/milarin/anilist v1.5.1/go.mod h1:8PTHXFMA45JpfRFIpcdrKwDHue8fbT/wwV1BuHFn6c0= +git.milar.in/milarin/channel v0.0.14 h1:1jBaKNNOK/mmMMgC7yVW1Cgnbb7q9aKBXZ5WnWptDek= +git.milar.in/milarin/channel v0.0.14/go.mod h1:We83LTI8S7u7II3pD+A2ChCDWJfCkcBUCUqii9HjTtM= +git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE= +git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY= +git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM= +git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE= +git.milar.in/milarin/slices v0.0.6 h1:AQoSarZ58WHYol9c6woWJSe8wFpPC2RC4cvIlZpfg9s= +git.milar.in/milarin/slices v0.0.6/go.mod h1:NOr53AOeur/qscu/FBj3lsFR262PNYBccLYSTCAXRk4= +git.milar.in/nyaanime/logic v0.0.0-20230113102709-a719289ef360 h1:pHbxMy5TnpOAc3TNEOoNIfcSe5epAEb2PSmedpWvnF4= +git.milar.in/nyaanime/logic v0.0.0-20230113102709-a719289ef360/go.mod h1:SkgdD87uNP60swIBtrDRZPBF5rLIgBLL6OetP1sETLY= +git.milar.in/nyaanime/model v0.0.0-20230113095840-5eb2822653c3 h1:mXcEA47FQQzeSDXE3UvhNfIt4fBfpDSq1/f0r+jbHpY= +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= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU= +gopkg.in/vansante/go-ffprobe.v2 v2.1.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE= diff --git a/handle_file.go b/handle_file.go new file mode 100644 index 0000000..bfa3571 --- /dev/null +++ b/handle_file.go @@ -0,0 +1,99 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "git.milar.in/milarin/adverr" + "git.milar.in/nyaanime/logic" + "git.milar.in/nyaanime/model" + "git.milar.in/nyaanime/parsers" +) + +func HandleFile(path string) { + for _, parser := range parsers.Parsers { + parsedFile, ok := parser.FileParser(&parser, path) + if !ok { + continue + } + + anime, err := logic.SearchAnimeByTitle(parsedFile.OriginalAnimeTitle) + if err != nil { + continue + } + + parsedFile.Anime = anime + HandleParsedFile(parsedFile) + } +} + +func HandleParsedFile(parsedFile *model.ParsedFile) { + newFilePrio := logic.NewFilePriority(parsedFile) + oldFilePrio, animeEpNotExistLocally := logic.GetAnimeEpProps(parsedFile.AnimeEpisode()) + + if !animeEpNotExistLocally || newFilePrio.Priority > oldFilePrio.Priority { + fmt.Println(animeEpNotExistLocally, newFilePrio, oldFilePrio) + go func(parsedFile *model.ParsedFile) { + if err := OrganizeAnimeEpisode(parsedFile); err != nil { + adverr.Println(err) + } + }(parsedFile) + } +} + +func OrganizeAnimeEpisode(parsedFile *model.ParsedFile) error { + oldFile := filepath.Join(DownloadPath, parsedFile.File) + newFile := logic.GetAnimeEpFilepath(parsedFile.AnimeEpisode(), filepath.Ext(parsedFile.File)) + lockFile := logic.GetAnimeEpFilepath(parsedFile.AnimeEpisode(), "lock") + + fmt.Printf("move file '%s' to '%s'\n", oldFile, newFile) + + if err := os.MkdirAll(filepath.Dir(newFile), os.ModePerm); err != nil { + return err + } + + if err := os.Chown(filepath.Dir(newFile), Uid, Gid); err != nil { + return err + } + + if _, err := os.Stat(newFile); !errors.Is(err, os.ErrNotExist) { + fmt.Fprintln(os.Stderr, "file already exists:", newFile) + return err + } + + inputFile, err := os.Open(oldFile) + if err != nil { + return err + } + defer inputFile.Close() + + outputFile, err := os.Create(newFile) + if err != nil { + return err + } + defer outputFile.Close() + + if err := os.Chown(newFile, Uid, Gid); err != nil { + return err + } + + _, err = io.Copy(outputFile, inputFile) + if err != nil { + return err + } + + if err = os.Remove(oldFile); err != nil { + return err + } + + if err = os.Remove(lockFile); err != nil { + return err + } + + fmt.Printf("file '%s' moved\n", newFile) + + return nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2430264 --- /dev/null +++ b/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "git.milar.in/nyaanime/logic" + "github.com/fsnotify/fsnotify" +) + +func main() { + // get access token once at startup to be sure that an access token is obtainable at all + if _, err := logic.GetAnilistAccessToken(); err != nil { + panic(err) // TODO error handling + } + + fsChan, err := WatchDirectory(fsnotify.Create, DownloadPath) + if err != nil { + panic(err) // TODO error handling + } + + for file := range fsChan { + HandleFile(file) + } +} diff --git a/watch_directory.go b/watch_directory.go new file mode 100644 index 0000000..10372cb --- /dev/null +++ b/watch_directory.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/fsnotify/fsnotify" +) + +func WatchDirectory(op fsnotify.Op, path string) (<-chan string, error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + panic(err) // TODO error handling + } + + err = watcher.Add(path) + if err != nil { + return nil, err + } + + files, err := os.ReadDir(path) + if err != nil { + return nil, err + } + + ch := make(chan string, 10) + + go func(watcher *fsnotify.Watcher, ch chan<- string) { + defer watcher.Close() + + for _, file := range files { + path := filepath.Join(path, file.Name()) + if file.IsDir() { + //fmt.Println("watching directory", path) + watcher.Add(path) + + if dirFiles, err := os.ReadDir(path); err == nil { + for _, dirFile := range dirFiles { + ch <- filepath.Join(path, dirFile.Name()) + } + } + } else { + ch <- path + } + } + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + close(ch) + return + } + + if fi, err := os.Stat(event.Name); err == nil && fi.IsDir() { + if event.Op&fsnotify.Create == fsnotify.Create { + //fmt.Println("watching directory", event.Name) + watcher.Add(event.Name) + } + + // read dir immediately because directory files could simultanously with its parent directory + if dirFiles, err := os.ReadDir(event.Name); err == nil { + for _, dirFile := range dirFiles { + ch <- filepath.Join(event.Name, dirFile.Name()) + } + } + } else if err != nil && event.Op&fsnotify.Remove == fsnotify.Remove { + //fmt.Println("stopped watching directory", event.Name) + watcher.Remove(event.Name) + } + + if event.Op&op == op { + ch <- event.Name + } + case err, ok := <-watcher.Errors: + fmt.Println(err, ok) + close(ch) + return + } + } + }(watcher, ch) + + return ch, nil +}