summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/cmd.go58
-rw-r--r--cmd/init.go38
-rw-r--r--files/database.go126
-rw-r--r--files/index.go53
-rw-r--r--files/types.go19
-rw-r--r--go.mod6
-rw-r--r--go.sum19
7 files changed, 290 insertions, 29 deletions
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 86a1b92..82a9cd5 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -1,14 +1,13 @@
package main
import (
- "fmt"
+ "context"
"os"
"os/signal"
- "time"
+ "github.com/justjakka/reencoder/files"
"github.com/tidwall/buntdb"
"github.com/urfave/cli/v2"
- "golang.org/x/sync/errgroup"
)
func runCmd(cCtx *cli.Context) error {
@@ -17,32 +16,51 @@ func runCmd(cCtx *cli.Context) error {
return err
}
- db, err := buntdb.Open(ctx.Value("database").(string))
+ db, err := buntdb.Open(ctx.Value("dbfile").(string))
if err != nil {
return err
}
defer db.Close()
+ if err := db.Shrink(); err != nil {
+ return err
+ }
+
+ if err := db.CreateIndex("process", "*", buntdb.IndexJSON("process")); err != nil {
+ if err != buntdb.ErrIndexExists {
+ return err
+ }
+ }
+
+ ctx = context.WithValue(ctx, "database", db)
+
ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
defer stop()
- test := new(errgroup.Group)
- test.SetLimit(3)
- for n := 1; n < 10; n++ {
- test.Go(func() error {
- select {
- case <-ctx.Done():
- return nil
- default:
- fmt.Println("test")
- time.Sleep(3 * time.Second)
- return nil
- }
- })
+ if err = files.IndexFlacs(ctx); err != nil {
+ return err
}
- _ = test.Wait()
- fmt.Println("reached")
- return cCtx.Err()
+
+ return nil
+
+ /*
+ test := new(errgroup.Group)
+ test.SetLimit(3)
+ for n := 1; n < 10; n++ {
+ test.Go(func() error {
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ fmt.Println("test")
+ time.Sleep(3 * time.Second)
+ return nil
+ }
+ })
+ }
+ _ = test.Wait()
+ fmt.Println("reached")
+ return cCtx.Err() */
}
func start() {
diff --git a/cmd/init.go b/cmd/init.go
index 836a04a..18e3b65 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -4,8 +4,10 @@ import (
"context"
"errors"
"os"
+ "os/exec"
"path/filepath"
"runtime"
+ "strings"
"github.com/urfave/cli/v2"
)
@@ -26,21 +28,35 @@ func getLocalStorage() string {
}
func getDb(cCtx *cli.Context) (context.Context, error) {
- if cCtx.Path("database") == "" {
+ if cCtx.Path("dbfile") == "" {
localFolder := getLocalStorage()
if localFolder == "" {
- return context.WithValue(cCtx.Context, "database", ""), errors.New("failed to locate application data folder")
+ return context.WithValue(cCtx.Context, "dbfile", ""), errors.New("failed to locate application data folder")
}
- return context.WithValue(cCtx.Context, "database", filepath.Join(localFolder, "reencoder.db")), nil
+ return context.WithValue(cCtx.Context, "dbfile", filepath.Join(localFolder, "reencoder.db")), nil
}
- if _, err := os.Stat(cCtx.Path("database")); err != nil {
- return context.WithValue(cCtx.Context, "database", ""), err
+ if _, err := os.Stat(cCtx.Path("dbfile")); err != nil {
+ return context.WithValue(cCtx.Context, "dbfile", ""), err
}
- return context.WithValue(cCtx.Context, "database", cCtx.Path("database")), nil
+ return context.WithValue(cCtx.Context, "dbfile", cCtx.Path("dbfile")), nil
+}
+
+func checkTools() error {
+ if _, err := exec.LookPath("flac"); err != nil {
+ return errors.New("missing flac executable")
+ }
+ if _, err := exec.LookPath("metaflac"); err != nil {
+ return errors.New("missing metaflac executable")
+ }
+ return nil
}
func initCmd(cCtx *cli.Context) (context.Context, error) {
+ if err := checkTools(); err != nil {
+ return nil, err
+ }
+
if _, err := os.Stat(cCtx.Path("path")); err != nil {
return nil, err
}
@@ -49,5 +65,13 @@ func initCmd(cCtx *cli.Context) (context.Context, error) {
if err != nil {
return nil, err
}
- return context.WithValue(ctx, "path", cCtx.Path("path")), nil
+
+ ctx = context.WithValue(ctx, "path", cCtx.Path("path"))
+
+ encoder, err := exec.Command("flac", "-v").Output()
+ if err != nil {
+ return nil, err
+ }
+
+ return context.WithValue(ctx, "encoder", strings.ReplaceAll(strings.Split(string(encoder), " ")[1], "\n", "")), nil
}
diff --git a/files/database.go b/files/database.go
new file mode 100644
index 0000000..24bd5af
--- /dev/null
+++ b/files/database.go
@@ -0,0 +1,126 @@
+package files
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/tidwall/buntdb"
+)
+
+func evaluateFile(database *buntdb.DB, hashsum string, filedata FileInfo) error {
+ return database.View(func(tx *buntdb.Tx) error {
+ data, err := tx.Get(hashsum)
+ if err != nil {
+ return err
+ }
+
+ var info FileInfo
+ if err := json.Unmarshal([]byte(data), &info); err != nil {
+ return err
+ }
+ if info == filedata {
+ return UpToDate
+ }
+ switch info.Modtime == filedata.Modtime {
+ case true:
+ if info.AbsPath == filedata.AbsPath {
+ if info.Process {
+ return NeedsReencode
+ }
+ return UpToDate
+ }
+ if info.Process {
+ return NeedsReencode
+ }
+ return MovedFile
+ default:
+ return nil
+ }
+ })
+}
+
+func getEncoderVer(path string) (string, error) {
+ out, err := exec.Command("metaflac", "--show-vendor-tag", path).Output()
+ if err != nil {
+ return "", err
+ }
+
+ r := regexp.MustCompile("libFLAC \\d\\.\\d\\.\\d")
+ encoder := r.FindString(string(out))
+ switch encoder {
+ case "":
+ return "", nil
+ default:
+ return strings.Split(encoder, " ")[1], nil
+ }
+}
+
+func updateFile(database *buntdb.DB, hashsum string, filedata FileInfo) error {
+ return database.Update(func(tx *buntdb.Tx) error {
+ data, err := json.Marshal(filedata)
+ if err != nil {
+ return err
+ }
+ if _, _, err := tx.Set(hashsum, string(data), nil); err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+func ProcessFile(ctx context.Context, path string, info fs.DirEntry) error {
+ tmp, err := info.Info()
+ if err != nil {
+ return err
+ }
+
+ modtime := tmp.ModTime()
+
+ database := ctx.Value("database").(*buntdb.DB)
+
+ 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
+ }
+ hashsum := fmt.Sprintf("%x", hash.Sum(nil))
+
+ abspath, err := filepath.Abs(path)
+ if err != nil {
+ return err
+ }
+
+ encoder, err := getEncoderVer(path)
+
+ filedata := FileInfo{AbsPath: abspath, Modtime: modtime, Encoder: encoder, Process: true}
+
+ err = evaluateFile(database, hashsum, filedata)
+ switch err {
+ case MovedFile, UpToDate:
+ if filedata.Encoder == ctx.Value("encoder").(string) {
+ return nil
+ }
+ case buntdb.ErrNotFound, NeedsReencode:
+
+ default:
+ return err
+ }
+ if err := updateFile(database, hashsum, filedata); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/files/index.go b/files/index.go
index e02e4ba..ea05dc4 100644
--- a/files/index.go
+++ b/files/index.go
@@ -1,7 +1,56 @@
-package main
+package files
-import "context"
+import (
+ "context"
+ "io/fs"
+ "path/filepath"
+ "time"
+
+ "github.com/briandowns/spinner"
+ "golang.org/x/sync/errgroup"
+)
func IndexFlacs(ctx context.Context) error {
+ spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+ spin.Suffix = " Indexing flacs..."
+
+ spin.Start()
+
+ wg := new(errgroup.Group)
+ wg.SetLimit(100)
+
+ if err := filepath.WalkDir(ctx.Value("path").(string), func(path string, info fs.DirEntry, err error) error {
+ select {
+ case <-ctx.Done():
+ spin.FinalMSG = "Stopping...\n"
+ spin.Stop()
+ return nil
+ default:
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ if filepath.Ext(path) == ".flac" {
+ wg.Go(func() error {
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ return ProcessFile(ctx, path, info)
+ }
+ })
+ }
+
+ }
+ return nil
+ }
+
+ }); err != nil {
+ return err
+ }
+
+ wg.Wait()
+ spin.FinalMSG = "Done indexing flacs"
+ spin.Stop()
return nil
}
diff --git a/files/types.go b/files/types.go
new file mode 100644
index 0000000..54d8563
--- /dev/null
+++ b/files/types.go
@@ -0,0 +1,19 @@
+package files
+
+import (
+ "errors"
+ "time"
+)
+
+var (
+ UpToDate = errors.New("file is up to date")
+ MovedFile = errors.New("file was moved")
+ NeedsReencode = errors.New("file needs to be reencoded")
+)
+
+type FileInfo struct {
+ AbsPath string `json:"abspath"`
+ Modtime time.Time `json:"modtime"`
+ Encoder string `json:"encoder"`
+ Process bool `json:"process"`
+}
diff --git a/go.mod b/go.mod
index 9c36db4..0c1e6dd 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,9 @@ go 1.24.2
require github.com/urfave/cli/v2 v2.27.6
require (
+ github.com/fatih/color v1.7.0 // indirect
+ github.com/mattn/go-colorable v0.1.2 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
github.com/tidwall/btree v1.4.2 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/grect v0.1.4 // indirect
@@ -12,9 +15,12 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
+ golang.org/x/sys v0.29.0 // indirect
+ golang.org/x/term v0.28.0 // indirect
)
require (
+ github.com/briandowns/spinner v1.23.2
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/buntdb v1.3.2
diff --git a/go.sum b/go.sum
index cdaf029..c088acc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,18 @@
+github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
+github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
+github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
@@ -11,6 +22,8 @@ github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
+github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
+github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
@@ -25,3 +38,9 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=