refactor: replace fileutil with fsutil and local hashing

This commit is contained in:
Mathis Maquenne
2026-08-05 11:42:35 +02:00
parent 12dbbb5e10
commit dbb808f371
9 changed files with 110 additions and 119 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/fsutil"
) )
type Config struct { type Config struct {
@@ -22,7 +22,7 @@ func Load() (*Config, error) {
} }
outputDir := filepath.Join(homeDir, "Music", "GoDeez") 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) return nil, fmt.Errorf("failed to create output directory: %w", err)
} }
+6 -6
View File
@@ -13,7 +13,7 @@ import (
"github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/config"
"github.com/mathismqn/godeez/internal/deezer" "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/store"
"github.com/mathismqn/godeez/internal/tag" "github.com/mathismqn/godeez/internal/tag"
) )
@@ -27,7 +27,7 @@ type Client struct {
deezerClient *deezer.Client deezerClient *deezer.Client
hashIndexOnce sync.Once hashIndexOnce sync.Once
hashIndex *fileutil.HashIndex hashIndex *hashIndex
hashIndexErr error hashIndexErr error
} }
@@ -85,7 +85,7 @@ func (c *Client) prepareResource(ctx context.Context, id string, opts Options) (
} }
outputDir := resource.GetOutputDir(c.appConfig.OutputDir) 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) 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) key := deezer.BlowfishKey(track.ID)
if err := c.streamToFile(dlCtx, stream, outputPath, key); err != nil { 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)} 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)) warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err))
} }
hash, err := fileutil.GetFileHash(outputPath) hash, err := hashFile(outputPath)
if err != nil { if err != nil {
warnings = append(warnings, fmt.Sprintf("failed to get file hash: %v", err)) 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 { func (c *Client) initHashIndex(ctx context.Context) error {
c.hashIndexOnce.Do(func() { 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 return c.hashIndexErr
+61
View File
@@ -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
}
+3 -3
View File
@@ -3,7 +3,7 @@ package downloader
import ( import (
"context" "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) { 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 return "", false
} }
if fileutil.FileExists(existing.Path) { if fsutil.Exists(existing.Path) {
return existing.Path, true return existing.Path, true
} }
@@ -24,7 +24,7 @@ func (c *Client) shouldSkipDownload(ctx context.Context, trackID, mediaFormat st
return "", false return "", false
} }
foundPath, ok := c.hashIndex.Find(existing.Hash) foundPath, ok := c.hashIndex.find(existing.Hash)
if !ok { if !ok {
return "", false return "", false
} }
-50
View File
@@ -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
}
-52
View File
@@ -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
}
+32
View File
@@ -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)
}
+4 -4
View File
@@ -13,7 +13,7 @@ import (
"strings" "strings"
"github.com/mathismqn/godeez/internal/buildinfo" "github.com/mathismqn/godeez/internal/buildinfo"
"github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/fsutil"
) )
var managedPrefixes = []string{ var managedPrefixes = []string{
@@ -100,7 +100,7 @@ func (u *Updater) Apply(ctx context.Context, release *Release) error {
if err != nil { if err != nil {
return err return err
} }
defer fileutil.DeleteFile(tmp) defer fsutil.Remove(tmp)
u.step("Verifying checksum") u.step("Verifying checksum")
if sum != want { if sum != want {
@@ -168,12 +168,12 @@ func (u *Updater) download(ctx context.Context, dir string, asset Asset) (string
hash := sha256.New() hash := sha256.New()
if _, err := io.Copy(io.MultiWriter(f, hash), body); err != nil { if _, err := io.Copy(io.MultiWriter(f, hash), body); err != nil {
f.Close() f.Close()
fileutil.DeleteFile(tmp) fsutil.Remove(tmp)
return "", "", fmt.Errorf("failed to download %s: %w", asset.Name, err) return "", "", fmt.Errorf("failed to download %s: %w", asset.Name, err)
} }
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
fileutil.DeleteFile(tmp) fsutil.Remove(tmp)
return "", "", err return "", "", err
} }
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/mathismqn/godeez/internal/buildinfo" "github.com/mathismqn/godeez/internal/buildinfo"
"github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/fsutil"
) )
const noCheckEnv = "GODEEZ_NO_UPDATE_CHECK" const noCheckEnv = "GODEEZ_NO_UPDATE_CHECK"
@@ -59,7 +59,7 @@ func writeCache(version string) error {
if err != nil { if err != nil {
return err return err
} }
if err := fileutil.EnsureDir(filepath.Dir(path)); err != nil { if err := fsutil.EnsureDir(filepath.Dir(path)); err != nil {
return err return err
} }