Add missing files

This commit is contained in:
majomi 2024-06-30 17:27:41 +02:00
parent 9452aaf8d4
commit 7ffc6c7712
Signed by: majomi
SSH Key Fingerprint: SHA256:VYTIGShChSqrGhLmmBZAmfYtjdyj+HLrfJ1Hb6d/0O8
3 changed files with 219 additions and 2 deletions

View File

@ -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

177
main.go
View File

@ -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())
}

View File

@ -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
);