179 lines
3.4 KiB
Go
179 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"git.m3d.pw/majomi/mmm/internal/db"
|
|
"github.com/dhowden/tag"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"io/fs"
|
|
"log"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type app struct{}
|
|
|
|
type FileScanner struct {
|
|
path string
|
|
ch chan string
|
|
stop chan struct{}
|
|
q db.Querier
|
|
d time.Duration
|
|
}
|
|
|
|
type MusicFile struct {
|
|
path string
|
|
tags tag.Metadata
|
|
}
|
|
|
|
func main() {
|
|
musicPath, ok := os.LookupEnv("MMM_DIR")
|
|
if !ok {
|
|
log.Fatal("MMM_DIR not set")
|
|
}
|
|
|
|
dsn, ok := os.LookupEnv("MMM_DB")
|
|
if !ok {
|
|
log.Fatal("MMM_DB not set")
|
|
}
|
|
|
|
pool, err := pgxpool.New(context.Background(), dsn)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
q := db.New(pool)
|
|
|
|
scanner := NewFileScanner(musicPath, q, time.Hour)
|
|
scanner.Scan()
|
|
|
|
}
|
|
|
|
func NewFileScanner(path string, q db.Querier, d time.Duration) *FileScanner {
|
|
return &FileScanner{path: path, ch: make(chan string), stop: make(chan struct{}), q: q, d: d}
|
|
}
|
|
|
|
func (f *FileScanner) Scan() {
|
|
go f.walkDir()
|
|
for path := range f.ch {
|
|
go func() {
|
|
exists, err := f.q.Exists(context.Background(), path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if exists {
|
|
return
|
|
}
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
tags, err := tag.ReadFrom(file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
m := MusicFile{
|
|
path: path,
|
|
tags: tags,
|
|
}
|
|
|
|
log.Printf("Adding to DB: %s: %+v", m.path, m.tags.Title())
|
|
|
|
track, err := f.q.AddTrack(context.Background(), db.AddTrackParams{
|
|
Path: m.path,
|
|
AlbumArtist: m.tags.AlbumArtist(),
|
|
Title: m.tags.Title(),
|
|
Album: m.tags.Album(),
|
|
Year: int32(m.tags.Year()),
|
|
Artist: m.tags.Artist(),
|
|
Genre: m.tags.Genre(),
|
|
Lyrics: m.tags.Lyrics(),
|
|
Composer: m.tags.Composer(),
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
log.Println("Added ", track.Title)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func (f *FileScanner) walkDir() {
|
|
defer close(f.ch)
|
|
ticker := time.NewTicker(f.d)
|
|
|
|
// Create walk function, so we can use it right when we start and don't need to wait for the ticker to tick
|
|
walk := func() {
|
|
filepath.WalkDir(f.path, func(path string, d fs.DirEntry, err error) error {
|
|
if d.IsDir() || filepath.Ext(path) != ".flac" {
|
|
slog.Debug("skipping directory: ", path)
|
|
return nil
|
|
}
|
|
f.ch <- path
|
|
return err
|
|
})
|
|
}
|
|
go walk()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
go walk()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *FileScanner) addToDB(path string) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
tags, err := tag.ReadFrom(file)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Println("Adding to DB: ", tags.Title())
|
|
}
|
|
|
|
func index(path string, wg *sync.WaitGroup) <-chan string {
|
|
fileChan := make(chan string)
|
|
go func(wg *sync.WaitGroup) {
|
|
filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
if d.IsDir() || filepath.Ext(path) != ".flac" {
|
|
slog.Debug("skipping directory: ", path)
|
|
return nil
|
|
}
|
|
fileChan <- path
|
|
wg.Add(1)
|
|
return err
|
|
})
|
|
close(fileChan)
|
|
}(wg)
|
|
return fileChan
|
|
}
|
|
|
|
func readTag(path string, wg *sync.WaitGroup) {
|
|
wg.Add(1)
|
|
defer wg.Done()
|
|
|
|
slog.Debug("reading tag", "path", path)
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
slog.Warn("failed to open file: ", path)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
tags, err := tag.ReadFrom(f)
|
|
if err != nil {
|
|
slog.Warn("failed to parse file", "path", path)
|
|
return
|
|
}
|
|
slog.Debug("tags", "title", tags.Title())
|
|
}
|