diff --git a/main.go b/main.go index 963b78a..ef754e0 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,14 @@ import ( "encoding/json" "errors" "fmt" + "io" "log" "os" "path" + "regexp" "strings" "text/template" + "time" "slices" @@ -24,14 +27,13 @@ type DownloadDirectoryComplete struct { Username string `json:"username"` } +var printLogs = true + 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") + log.Printf("WARNING: Given data version not supported. Supported versions: %s. Given version: %d. Continuing, but there may be demons\n", supportedVersionsStr, d.Version) } } @@ -43,10 +45,6 @@ func main() { } func mainErr() error { - if len(os.Args) < 2 { - return errors.New("missing data argument") - } - if _, err := os.Stat(".env"); err == nil { if err := godotenv.Load(".env"); err != nil { return err @@ -60,22 +58,41 @@ func mainErr() error { return err } } + + directory, err := getDir() + if err != nil { + return err + } + + return mainPath(directory) +} + +func getDir() (string, error) { + if len(os.Args) > 1 { + return os.Args[1], nil + } else { + dataArg := os.Getenv("SLSKD_SCRIPT_DATA") + var data DownloadDirectoryComplete + if err := json.Unmarshal([]byte(dataArg), &data); err != nil { + return "", err + } + + data.WarnVersion() + printLogs = false + + return data.LocalDirectoryName, nil + } +} + +func mainPath(directory string) error { 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) + files, err := os.ReadDir(directory) if err != nil { return err } @@ -84,18 +101,22 @@ func mainErr() error { } // read tags - tags, _ := getTagsInDir(data.LocalDirectoryName) + tags, _ := getTagsInDir(directory) // move files if tags != nil { - log.Println("found tags, moving based on tags") - err := doMoveTags(data.LocalDirectoryName, tags) + if printLogs { + log.Println("found tags, moving based on tags") + } + err := doMoveTags(directory, tags) if err != nil { return err } } else { - log.Println("no tags found, moving to notags folder") - err := doMoveNotags(data.LocalDirectoryName) + if printLogs { + log.Println("no tags found, moving to notags folder") + } + err := doMoveNotags(directory) if err != nil { return err } @@ -134,27 +155,21 @@ func getTagsInDir(directory string) (tag.Metadata, error) { 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 } @@ -171,7 +186,7 @@ func doMoveNotags(directory string) error { _, folderName := path.Split(directory) toDirectory := path.Join(newRoot, folderName) - return rename(directory, toDirectory) + return move(directory, toDirectory) } func doMoveTags(directory string, albumTags tag.Metadata) error { @@ -186,6 +201,10 @@ func doMoveTags(directory string, albumTags tag.Metadata) error { continue } + if file.Name()[0] == '.' { + continue + } + fname := path.Join(directory, file.Name()) file, err := os.Open(fname) if err != nil { @@ -211,6 +230,10 @@ func doMoveTags(directory string, albumTags tag.Metadata) error { } for _, file := range files { + if file.Name()[0] == '.' { + continue + } + fname := path.Join(directory, file.Name()) err = doMoveTaggedPathonly(fname, albumTags) @@ -220,7 +243,27 @@ func doMoveTags(directory string, albumTags tag.Metadata) error { } // delete folder - return os.Remove(directory) + files, err = os.ReadDir(directory) + if err != nil { + return err + } + + if len(files) == 0 { + return os.Remove(directory) + } + + // some files remain, wait a second to see if they go away + time.Sleep(1 * time.Second) + files, err = os.ReadDir(directory) + if err != nil { + return err + } + + if len(files) == 0 { + return os.Remove(directory) + } + + return nil } type LibTemplatePath struct { @@ -291,7 +334,7 @@ func doMoveTagged(filePath string, fileTags tag.Metadata) error { libFile := path.Join(libRoot, bufPath.String(), bufFile.String() + fileExt) - return rename(filePath, libFile) + return move(filePath, libFile) } func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error { @@ -316,7 +359,7 @@ func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error { libFile := path.Join(libRoot, bufPath.String(), fileName) - return rename(filePath, libFile) + return move(filePath, libFile) } func ensureDirectoryPath(filePath string) error { @@ -324,7 +367,8 @@ func ensureDirectoryPath(filePath string) error { return os.MkdirAll(directoryPath, 0777) } -func rename(from, to string) error { +func move(from, to string) error { + to = cleanPath(to) if _, err := os.Stat(to); err == nil { return fmt.Errorf("file already exists: %s", to) } @@ -333,6 +377,55 @@ func rename(from, to string) error { return err } - fmt.Printf("renaming \"%s\" => \"%s\"\n", from, to) - return os.Rename(from, to) + if printLogs { + log.Printf("moving \"%s\" => \"%s\"\n", from, to) + } + // os.Rename gives error "invalid cross-device link" + if err := os.Rename(from, to); err != nil { + return moveFile(from, to) + } + return nil +} + +func moveFile(source, destination string) (err error) { + src, err := os.Open(source) + if err != nil { + return err + } + defer src.Close() + fi, err := src.Stat() + if err != nil { + return err + } + flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC + perm := fi.Mode() & os.ModePerm + dst, err := os.OpenFile(destination, flag, perm) + if err != nil { + return err + } + defer dst.Close() + _, err = io.Copy(dst, src) + if err != nil { + dst.Close() + os.Remove(destination) + return err + } + err = dst.Close() + if err != nil { + return err + } + err = src.Close() + if err != nil { + return err + } + err = os.Remove(source) + if err != nil { + return err + } + return nil +} + +func cleanPath(str string) string { + re := regexp.MustCompile(`[<>:"|?*]`) + return re.ReplaceAllString(str, "") } diff --git a/readme.md b/readme.md index af812ce..713a42d 100644 --- a/readme.md +++ b/readme.md @@ -2,4 +2,6 @@ move music files in a directory to an organized folder based on tags -for now, this only supports slskd's $DATA arg as input +for now, this only supports slskd's ~~$DATA arg as input~~ SLSKD_SCRIPT_DATA environment variable as input + +(why is this shit not documented well)