diff --git a/internal/config/config.go b/internal/config/config.go index 23d5898..83c1957 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/mathismqn/godeez/internal/fileutil" + "github.com/mathismqn/godeez/internal/fsutil" ) type Config struct { @@ -22,7 +22,7 @@ func Load() (*Config, error) { } outputDir := filepath.Join(homeDir, "Music", "GoDeez") - if err := fileutil.EnsureDir(outputDir); err != nil { + if err := fsutil.EnsureDir(outputDir); err != nil { return nil, fmt.Errorf("failed to create output directory: %w", err) } diff --git a/internal/downloader/client.go b/internal/downloader/client.go index 33a4567..6b7f6df 100644 --- a/internal/downloader/client.go +++ b/internal/downloader/client.go @@ -13,7 +13,7 @@ import ( "github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/deezer" - "github.com/mathismqn/godeez/internal/fileutil" + "github.com/mathismqn/godeez/internal/fsutil" "github.com/mathismqn/godeez/internal/store" "github.com/mathismqn/godeez/internal/tag" ) @@ -27,7 +27,7 @@ type Client struct { deezerClient *deezer.Client hashIndexOnce sync.Once - hashIndex *fileutil.HashIndex + hashIndex *hashIndex hashIndexErr error } @@ -85,7 +85,7 @@ func (c *Client) prepareResource(ctx context.Context, id string, opts Options) ( } outputDir := resource.GetOutputDir(c.appConfig.OutputDir) - if err := fileutil.EnsureDir(outputDir); err != nil { + if err := fsutil.EnsureDir(outputDir); err != nil { return nil, "", fmt.Errorf("failed to create output directory: %w", err) } @@ -156,7 +156,7 @@ func (c *Client) downloadTrack(ctx context.Context, resource deezer.Resource, tr key := deezer.BlowfishKey(track.ID) if err := c.streamToFile(dlCtx, stream, outputPath, key); err != nil { - fileutil.DeleteFile(outputPath) + fsutil.Remove(outputPath) return downloadResult{err: fmt.Errorf("failed to stream to file: %w", err)} } @@ -237,7 +237,7 @@ func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track, warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err)) } - hash, err := fileutil.GetFileHash(outputPath) + hash, err := hashFile(outputPath) if err != nil { warnings = append(warnings, fmt.Sprintf("failed to get file hash: %v", err)) } @@ -259,7 +259,7 @@ func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track, func (c *Client) initHashIndex(ctx context.Context) error { c.hashIndexOnce.Do(func() { - c.hashIndex, c.hashIndexErr = fileutil.NewHashIndex(ctx, c.appConfig.OutputDir) + c.hashIndex, c.hashIndexErr = newHashIndex(ctx, c.appConfig.OutputDir) }) return c.hashIndexErr diff --git a/internal/downloader/hash.go b/internal/downloader/hash.go new file mode 100644 index 0000000..bb52d5d --- /dev/null +++ b/internal/downloader/hash.go @@ -0,0 +1,61 @@ +package downloader + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "io" + "io/fs" + "os" + "path/filepath" +) + +func hashFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + + h := sha256.New() + if _, err := io.Copy(h, file); err != nil { + return "", err + } + + return hex.EncodeToString(h.Sum(nil)), nil +} + +type hashIndex struct { + files map[string]string +} + +func newHashIndex(ctx context.Context, root string) (*hashIndex, error) { + index := &hashIndex{files: make(map[string]string)} + + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if ctx.Err() != nil { + return ctx.Err() + } + if err != nil || d.IsDir() { + return nil + } + + hash, err := hashFile(path) + if err != nil { + return nil + } + + index.files[hash] = path + return nil + }) + if err != nil { + return nil, err + } + + return index, nil +} + +func (h *hashIndex) find(hash string) (string, bool) { + path, ok := h.files[hash] + return path, ok +} diff --git a/internal/downloader/skip.go b/internal/downloader/skip.go index 1fbc19a..9b57b11 100644 --- a/internal/downloader/skip.go +++ b/internal/downloader/skip.go @@ -3,7 +3,7 @@ package downloader import ( "context" - "github.com/mathismqn/godeez/internal/fileutil" + "github.com/mathismqn/godeez/internal/fsutil" ) func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat string) (string, bool) { @@ -12,7 +12,7 @@ func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat st return "", false } - if fileutil.FileExists(existing.Path) { + if fsutil.Exists(existing.Path) { return existing.Path, true } @@ -24,7 +24,7 @@ func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat st return "", false } - foundPath, ok := c.hashIndex.Find(existing.Hash) + foundPath, ok := c.hashIndex.find(existing.Hash) if !ok { return "", false } diff --git a/internal/fileutil/file.go b/internal/fileutil/file.go deleted file mode 100644 index 6aab16b..0000000 --- a/internal/fileutil/file.go +++ /dev/null @@ -1,50 +0,0 @@ -package fileutil - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "os" -) - -func EnsureDir(path string) error { - info, err := os.Stat(path) - if os.IsNotExist(err) { - return os.MkdirAll(path, 0755) - } - if err != nil { - return err - } - if !info.IsDir() { - return fmt.Errorf("file already exists at %s", path) - } - return nil -} - -func FileExists(path string) bool { - info, err := os.Stat(path) - return err == nil && !info.IsDir() -} - -func DeleteFile(path string) error { - if !FileExists(path) { - return nil - } - return os.Remove(path) -} - -func GetFileHash(path string) (string, error) { - file, err := os.Open(path) - if err != nil { - return "", err - } - defer file.Close() - - h := sha256.New() - if _, err := io.Copy(h, file); err != nil { - return "", err - } - - return hex.EncodeToString(h.Sum(nil)), nil -} diff --git a/internal/fileutil/hashindex.go b/internal/fileutil/hashindex.go deleted file mode 100644 index f5071e8..0000000 --- a/internal/fileutil/hashindex.go +++ /dev/null @@ -1,52 +0,0 @@ -package fileutil - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "io" - "io/fs" - "os" - "path/filepath" -) - -type HashIndex struct { - files map[string]string -} - -func NewHashIndex(ctx context.Context, root string) (*HashIndex, error) { - index := &HashIndex{files: make(map[string]string)} - - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if ctx.Err() != nil { - return ctx.Err() - } - if err != nil || d.IsDir() { - return nil - } - - file, err := os.Open(path) - if err != nil { - return nil - } - defer file.Close() - - h := sha256.New() - if _, err := io.Copy(h, file); err != nil { - return nil - } - - index.files[hex.EncodeToString(h.Sum(nil))] = path - return nil - }) - if err != nil { - return nil, err - } - - return index, nil -} - -func (h *HashIndex) Find(hash string) (string, bool) { - path, ok := h.files[hash] - return path, ok -} diff --git a/internal/fsutil/fsutil.go b/internal/fsutil/fsutil.go new file mode 100644 index 0000000..3fc38c5 --- /dev/null +++ b/internal/fsutil/fsutil.go @@ -0,0 +1,32 @@ +package fsutil + +import ( + "fmt" + "os" +) + +func EnsureDir(path string) error { + info, err := os.Stat(path) + if os.IsNotExist(err) { + return os.MkdirAll(path, 0755) + } + if err != nil { + return err + } + if !info.IsDir() { + return fmt.Errorf("file already exists at %s", path) + } + return nil +} + +func Exists(path string) bool { + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} + +func Remove(path string) error { + if !Exists(path) { + return nil + } + return os.Remove(path) +} diff --git a/internal/updater/apply.go b/internal/updater/apply.go index 9a4c549..38c0704 100644 --- a/internal/updater/apply.go +++ b/internal/updater/apply.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/mathismqn/godeez/internal/buildinfo" - "github.com/mathismqn/godeez/internal/fileutil" + "github.com/mathismqn/godeez/internal/fsutil" ) var managedPrefixes = []string{ @@ -100,7 +100,7 @@ func (u *Updater) Apply(ctx context.Context, release *Release) error { if err != nil { return err } - defer fileutil.DeleteFile(tmp) + defer fsutil.Remove(tmp) u.step("Verifying checksum") if sum != want { @@ -168,12 +168,12 @@ func (u *Updater) download(ctx context.Context, dir string, asset Asset) (string hash := sha256.New() if _, err := io.Copy(io.MultiWriter(f, hash), body); err != nil { f.Close() - fileutil.DeleteFile(tmp) + fsutil.Remove(tmp) return "", "", fmt.Errorf("failed to download %s: %w", asset.Name, err) } if err := f.Close(); err != nil { - fileutil.DeleteFile(tmp) + fsutil.Remove(tmp) return "", "", err } diff --git a/internal/updater/check.go b/internal/updater/check.go index a68ed27..fd5c686 100644 --- a/internal/updater/check.go +++ b/internal/updater/check.go @@ -8,7 +8,7 @@ import ( "time" "github.com/mathismqn/godeez/internal/buildinfo" - "github.com/mathismqn/godeez/internal/fileutil" + "github.com/mathismqn/godeez/internal/fsutil" ) const noCheckEnv = "GODEEZ_NO_UPDATE_CHECK" @@ -59,7 +59,7 @@ func writeCache(version string) error { if err != nil { return err } - if err := fileutil.EnsureDir(filepath.Dir(path)); err != nil { + if err := fsutil.EnsureDir(filepath.Dir(path)); err != nil { return err }