package main import ( "bytes" "encoding/json" "errors" "fmt" "log" "os" "path" "strings" "text/template" "slices" "github.com/dhowden/tag" "github.com/joho/godotenv" // _ "github.com/joho/godotenv/autoload" ) type DownloadDirectoryComplete struct { Version int `json:"version"` LocalDirectoryName string `json:"localDirectoryName"` RemoteDirectoryName string `json:"remoteDirectoryName"` Username string `json:"username"` } func (d *DownloadDirectoryComplete) WarnVersion() { supportedVersions := []int{ 0 } if !slices.Contains(supportedVersions, d.Version) { supportedVersionsStr := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(supportedVersions)), ","), "[]") log.Println("WARNING: Given data version not supported.") log.Printf( "WARNING: Supported versions: %s\n", supportedVersionsStr) log.Printf( "WARNING: Given version: %d\n", d.Version) log.Println("WARNING: Continuing, but there may be demons") } } func main() { err := mainErr() if err != nil { log.Fatal("ERROR: ", err) } } func mainErr() error { if len(os.Args) < 2 { return errors.New("missing data argument") } if err := godotenv.Load(); err != nil { return err } if err := testEnv(); err != nil { return err } // parse $DATA log.Println("Parsing slskd data") dataArg := os.Args[1] var data DownloadDirectoryComplete if err := json.Unmarshal([]byte(dataArg), &data); err != nil { return err } data.WarnVersion() files, err := os.ReadDir(data.LocalDirectoryName) if err != nil { return err } if len(files) == 0 { return errors.New("no files found") } // read tags tags, _ := getTagsInDir(data.LocalDirectoryName) // move files if tags != nil { log.Println("found tags, moving based on tags") err := doMoveTags(data.LocalDirectoryName, tags) if err != nil { return err } } else { log.Println("no tags found, moving to notags folder") err := doMoveNotags(data.LocalDirectoryName) if err != nil { return err } } return nil } func testEnv() error { _, ok := os.LookupEnv("LIBRARY_ROOT") if !ok { return errors.New("missing LIBRARY_ROOT") } _, ok = os.LookupEnv("LIBRARY_FILETEMPLATE_PATH") if !ok { return errors.New("missing LIBRARY_FILETEMPLATE_PATH") } _, ok = os.LookupEnv("LIBRARY_FILETEMPLATE_FILE") if !ok { return errors.New("missing LIBRARY_FILETEMPLATE_FILE") } _, ok = os.LookupEnv("LIBRARY_NOTAGS_ROOT") if !ok { return errors.New("missing LIBRARY_NOTAGS_ROOT") } return nil } func getTagsInDir(directory string) (tag.Metadata, error) { files, err := os.ReadDir(directory) if err != nil { return nil, err } log.Printf("DIRECTORY: %s\n", directory) for _, file := range files { fname := path.Join(directory, file.Name()) log.Printf(" FILE: %s\n", fname) if file.IsDir() { log.Print(" IS DIRECTORY\n") continue } file, err := os.Open(fname) if err != nil { log.Print(" CANT OPEN\n") continue } defer file.Close() tags, err := tag.ReadFrom(file) if err != nil { log.Print(" NO TAGS\n") continue } // found tag data return tags, nil } // no tag data in any file return nil, errors.New("missing tag data") } func doMoveNotags(directory string) error { newRoot := os.Getenv("LIBRARY_NOTAGS_ROOT") _, folderName := path.Split(directory) toDirectory := path.Join(newRoot, folderName) return rename(directory, toDirectory) } func doMoveTags(directory string, albumTags tag.Metadata) error { // move all tagged files files, err := os.ReadDir(directory) if err != nil { return err } for _, file := range files { if file.IsDir() { continue } fname := path.Join(directory, file.Name()) file, err := os.Open(fname) if err != nil { continue } defer file.Close() tags, err := tag.ReadFrom(file) if err != nil { continue } err = doMoveTagged(fname, tags) if err != nil { return err } } // move the rest files, err = os.ReadDir(directory) if err != nil { return err } for _, file := range files { fname := path.Join(directory, file.Name()) err = doMoveTaggedPathonly(fname, albumTags) if err != nil { return err } } // delete folder return os.Remove(directory) } type LibTemplatePath struct { Album string AlbumArtist string Genre string Year int } type LibTemplateFile struct { Title string Album string Artist string AlbumArtist string Composer string Genre string Year int Track int TrackCount int Disc int DiscCount int } func doMoveTagged(filePath string, fileTags tag.Metadata) error { libRoot := os.Getenv("LIBRARY_ROOT") libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH") libTemplateFile := os.Getenv("LIBRARY_FILETEMPLATE_FILE") fileExt := path.Ext(filePath) track, trackCount := fileTags.Track() disc, discCount := fileTags.Disc() tmplPath := template.Must(template.New("libPath").Parse(libTemplatePath)) tmplFile := template.Must(template.New("libFile").Parse(libTemplateFile)) tmplDataFile := LibTemplateFile{ Title: fileTags.Title(), Album: fileTags.Album(), Artist: fileTags.Artist(), AlbumArtist: fileTags.AlbumArtist(), Composer: fileTags.Composer(), Genre: fileTags.Genre(), Year: fileTags.Year(), Track: track, TrackCount: trackCount, Disc: disc, DiscCount: discCount, } tmplDataPath := LibTemplatePath{ Album: fileTags.Album(), AlbumArtist: fileTags.AlbumArtist(), Genre: fileTags.Genre(), Year: fileTags.Year(), } bufPath := bytes.NewBufferString("") err := tmplPath.Execute(bufPath, tmplDataPath) if err != nil { return err } bufFile := bytes.NewBufferString("") err = tmplFile.Execute(bufFile, tmplDataFile) if err != nil { return err } libFile := path.Join(libRoot, bufPath.String(), bufFile.String() + fileExt) return rename(filePath, libFile) } func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error { libRoot := os.Getenv("LIBRARY_ROOT") libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH") _, fileName := path.Split(filePath) tmplPath := template.Must(template.New("libPath").Parse(libTemplatePath)) tmplData := LibTemplatePath{ Album: albumTags.Album(), AlbumArtist: albumTags.AlbumArtist(), Genre: albumTags.Genre(), Year: albumTags.Year(), } bufPath := bytes.NewBufferString("") err := tmplPath.Execute(bufPath, tmplData) if err != nil { return err } libFile := path.Join(libRoot, bufPath.String(), fileName) return rename(filePath, libFile) } func ensureDirectoryPath(filePath string) error { directoryPath, _ := path.Split(filePath) return os.MkdirAll(directoryPath, 0777) } func rename(from, to string) error { if _, err := os.Stat(to); err == nil { return fmt.Errorf("file already exists: %s", to) } if err := ensureDirectoryPath(to); err != nil { return err } fmt.Printf("renaming \"%s\" => \"%s\"", from, to) return os.Rename(from, to) }