refactor: replace fileutil with fsutil and local hashing
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user