Add missing files
This commit is contained in:
parent
9452aaf8d4
commit
7ffc6c7712
@ -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
|
173
main.go
173
main.go
@ -1,5 +1,178 @@
|
||||
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())
|
||||
}
|
||||
|
26
table.sql
26
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
|
||||
);
|
Loading…
Reference in New Issue
Block a user