From 917ac5f6c205624853b32d3cab865a6eac244ed8 Mon Sep 17 00:00:00 2001 From: Mathis Maquenne <124215603+mathismqn@users.noreply.github.com> Date: Sat, 26 Apr 2025 14:10:27 +0200 Subject: [PATCH] feat: retrieve modified file paths using hashes --- cmd/download.go | 43 +++++++++++++------------- cmd/root.go | 44 +++++++++++++++++++++++++-- internal/config/config.go | 19 ++---------- internal/db/download_info.go | 1 + internal/db/setup.go | 17 ++--------- internal/utils/file.go | 58 +++++++++++++++++++++++++++++++++++- 6 files changed, 127 insertions(+), 55 deletions(-) diff --git a/cmd/download.go b/cmd/download.go index 047c565..a582b79 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -16,8 +16,10 @@ import ( "github.com/spf13/cobra" ) -var outputDir string -var quality string +var ( + outputDir string + quality string +) var downloadCmd = &cobra.Command{ Use: "download", @@ -32,23 +34,7 @@ func init() { func validateInput() { if outputDir == "" { - homeDir, err := os.UserHomeDir() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err) - os.Exit(1) - } - - musicDir := path.Join(homeDir, "Music") - if err := utils.EnsureDir(musicDir); err != nil { - fmt.Fprintf(os.Stderr, "Error: could not create music directory: %v\n", err) - os.Exit(1) - } - - outputDir = path.Join(musicDir, "GoDeez") - if err := utils.EnsureDir(outputDir); err != nil { - fmt.Fprintf(os.Stderr, "Error: could not create GoDeez directory: %v\n", err) - os.Exit(1) - } + outputDir = appDir } if quality == "" { @@ -171,11 +157,20 @@ func downloadContent(contentType string, args []string) { fileName, _ = filenamify.Filenamify(fileName, filenamify.Options{}) filePath := path.Join(output, fileName) - if existing, err := db.Get(song.ID); err == nil { - if existing.Quality == media.Data[0].Media[0].Format && utils.FileExists(existing.Path) { + if existing, err := db.Get(song.ID); err == nil && existing.Quality == media.Data[0].Media[0].Format { + if utils.FileExists(existing.Path) { fmt.Printf(" Skipping %s (already downloaded at %s)\n", songTitle, existing.Path) continue } + if existing.Hash != "" { + if foundPath, err := utils.FindFileByHash(appDir, existing.Hash); err == nil && foundPath != "" { + existing.Path = foundPath + _ = existing.Save() + fmt.Printf(" Recovered %s at %s, skipping download\n", songTitle, foundPath) + + continue + } + } } fmt.Printf(" Downloading %s...", songTitle) @@ -202,10 +197,16 @@ func downloadContent(contentType string, args []string) { fmt.Fprintf(os.Stderr, "Warning: could not add tags to song: %v\n", err) } + hash, err := utils.GetFileHash(filePath) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: could not get file hash: %v\n", err) + } + info := &db.DownloadInfo{ SongID: song.ID, Quality: media.Data[0].Media[0].Format, Path: filePath, + Hash: hash, Downloaded: time.Now(), } if err := info.Save(); err != nil { diff --git a/cmd/root.go b/cmd/root.go index 077a854..8fa357f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,12 +1,23 @@ package cmd import ( + "fmt" + "os" + "path" + "path/filepath" + "github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/db" + "github.com/mathismqn/godeez/internal/utils" "github.com/spf13/cobra" ) -var cfgFile string +var ( + cfgDir string + musicDir string + appDir string + cfgFile string +) var RootCmd = &cobra.Command{ Use: "godeez", @@ -19,7 +30,34 @@ var RootCmd = &cobra.Command{ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.godeez)") cobra.OnInitialize(func() { - config.Init(cfgFile) - db.Init() + initDirs() + config.Init(cfgFile, cfgDir) + db.Init(cfgDir) }) } + +func initDirs() { + home, err := os.UserHomeDir() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err) + os.Exit(1) + } + + cfgDir = filepath.Join(home, ".godeez") + if err := utils.EnsureDir(cfgDir); err != nil { + fmt.Fprintf(os.Stderr, "Error: could not create app directory: %v\n", err) + os.Exit(1) + } + + musicDir = filepath.Join(home, "Music") + if err := utils.EnsureDir(musicDir); err != nil { + fmt.Fprintf(os.Stderr, "Error: could not create music directory: %v\n", err) + os.Exit(1) + } + + appDir = path.Join(musicDir, "GoDeez") + if err := utils.EnsureDir(appDir); err != nil { + fmt.Fprintf(os.Stderr, "Error: could not create GoDeez directory: %v\n", err) + os.Exit(1) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 8412315..0c55016 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,7 +5,6 @@ import ( "os" "path" - "github.com/mathismqn/godeez/internal/utils" "github.com/spf13/viper" ) @@ -17,23 +16,11 @@ type Config struct { var Cfg Config -func Init(cfgFile string) { +func Init(cfgFile, cfgDir string) { if cfgFile != "" { viper.SetConfigFile(cfgFile) } else { - homeDir, err := os.UserHomeDir() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err) - os.Exit(1) - } - - appDir := path.Join(homeDir, ".godeez") - if err := utils.EnsureDir(appDir); err != nil { - fmt.Fprintf(os.Stderr, "Error: could not create app directory: %v\n", err) - os.Exit(1) - } - - cfgPath := path.Join(appDir, "config.toml") + cfgPath := path.Join(cfgDir, "config.toml") if _, err := os.Stat(cfgPath); os.IsNotExist(err) { fmt.Printf("Config file not found, creating one at %s\n", cfgPath) @@ -44,7 +31,7 @@ func Init(cfgFile string) { } } - viper.AddConfigPath(appDir) + viper.AddConfigPath(cfgDir) viper.SetConfigName("config.toml") } diff --git a/internal/db/download_info.go b/internal/db/download_info.go index 1a16ce0..00fd911 100644 --- a/internal/db/download_info.go +++ b/internal/db/download_info.go @@ -12,6 +12,7 @@ type DownloadInfo struct { SongID string `json:"song_id"` Quality string `json:"quality"` Path string `json:"path"` + Hash string `json:"hash"` Downloaded time.Time `json:"downloaded_at"` } diff --git a/internal/db/setup.go b/internal/db/setup.go index 8ac564f..d449402 100644 --- a/internal/db/setup.go +++ b/internal/db/setup.go @@ -5,7 +5,6 @@ import ( "os" "path" - "github.com/mathismqn/godeez/internal/utils" bolt "go.etcd.io/bbolt" ) @@ -14,20 +13,10 @@ var ( trackBucket = []byte("tracks") ) -func Init() { - homeDir, err := os.UserHomeDir() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err) - os.Exit(1) - } +func Init(cfgDir string) { + var err error - appDir := path.Join(homeDir, ".godeez") - if err := utils.EnsureDir(appDir); err != nil { - fmt.Fprintf(os.Stderr, "Error: could not create app directory: %v\n", err) - os.Exit(1) - } - - dbPath := path.Join(appDir, "tracks.db") + dbPath := path.Join(cfgDir, "tracks.db") db, err = bolt.Open(dbPath, 0600, nil) if err != nil { fmt.Fprintf(os.Stderr, "Error: could not open database: %v\n", err) diff --git a/internal/utils/file.go b/internal/utils/file.go index 114e18d..f6dadad 100644 --- a/internal/utils/file.go +++ b/internal/utils/file.go @@ -1,6 +1,13 @@ package utils -import "os" +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" +) func FileExists(path string) bool { info, err := os.Stat(path) @@ -15,3 +22,52 @@ func DeleteFile(path string) error { return os.Remove(path) } + +func GetFileHash(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} + +func FindFileByHash(root, targetHash string) (string, error) { + var found string + + if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + f, err := os.Open(path) + if err != nil { + return nil + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return nil + } + sum := hex.EncodeToString(h.Sum(nil)) + if sum == targetHash { + found = path + return filepath.SkipDir + } + return nil + }); err != nil { + return "", err + } + + return found, nil +}