diff --git a/docker-compose.yml b/docker-compose.yml index e69de29..7ea9f0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +volumes: + mmm-db: + +services: + mmm: + build: + context: . + db: + image: library/postgres:15 + container_name: mmm-postgres + restart: no + environment: + POSTGRES_USER: mmm + POSTGRES_PASSWORD: mmm + volumes: + - mmm-db:/var/lib/postgresql/data + ports: + - 5432:5432 \ No newline at end of file diff --git a/main.go b/main.go index fe7f767..68faee9 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,178 @@ package main -func 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()) } diff --git a/table.sql b/table.sql index e69de29..31ddf04 100644 --- a/table.sql +++ b/table.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS track +( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + path TEXT UNIQUE, + album_artist TEXT, + title TEXT, + album TEXT, + disc INTEGER, + year INTEGER, + artist TEXT[], + genre TEXT, + lyrics TEXT, + composer TEXT +); + +CREATE TABLE IF NOT EXISTS album +( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + path TEXT UNIQUE +); + +CREATE TABLE IF NOT EXISTS artist +( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + path TEXT UNIQUE +); \ No newline at end of file