Add jukebox_maker web app v1.0
Go web application for filling USB drives with media files. Runs in Docker on Unraid with /media, /mnt/usb, /config volumes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
149
cmd/jukebox/main.go
Normal file
149
cmd/jukebox/main.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"jukebox_maker/internal/api"
|
||||
"jukebox_maker/internal/config"
|
||||
"jukebox_maker/internal/copier"
|
||||
"jukebox_maker/internal/db"
|
||||
"jukebox_maker/internal/disk"
|
||||
"jukebox_maker/internal/task"
|
||||
"jukebox_maker/internal/watcher"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "/config/config.json", "path to config file")
|
||||
addr := flag.String("addr", ":8080", "HTTP listen address")
|
||||
mediaPath := flag.String("media", "/media", "path to media source directory")
|
||||
mountPath := flag.String("mount", "/mnt/usb", "path to USB mount point")
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("load config: %v", err)
|
||||
}
|
||||
|
||||
taskStore := task.NewStore()
|
||||
cp := copier.New(taskStore)
|
||||
|
||||
var activeDB *db.DB
|
||||
var activeDiskID string
|
||||
|
||||
openDiskDB := func(info disk.DiskInfo) {
|
||||
if activeDiskID == info.DiskID {
|
||||
return // already open for this disk
|
||||
}
|
||||
if activeDB != nil {
|
||||
activeDB.Close()
|
||||
activeDB = nil
|
||||
activeDiskID = ""
|
||||
}
|
||||
d, err := db.Open(disk.DBPath(info.MountPath))
|
||||
if err != nil {
|
||||
log.Printf("open disk DB: %v", err)
|
||||
return
|
||||
}
|
||||
activeDB = d
|
||||
activeDiskID = info.DiskID
|
||||
cp.SetDB(d)
|
||||
log.Printf("disk DB opened for %s", info.DiskID)
|
||||
}
|
||||
|
||||
closeDiskDB := func() {
|
||||
if activeDB != nil {
|
||||
activeDB.Close()
|
||||
activeDB = nil
|
||||
activeDiskID = ""
|
||||
cp.SetDB(nil)
|
||||
log.Println("disk DB closed")
|
||||
}
|
||||
}
|
||||
|
||||
w := watcher.New(*mountPath, 5*time.Second, func(ev watcher.DiskEvent) {
|
||||
log.Printf("disk: %s -> %s", ev.Prev, ev.Info.State)
|
||||
switch ev.Info.State {
|
||||
case disk.DiskKnown:
|
||||
openDiskDB(ev.Info)
|
||||
if ev.Prev != disk.DiskKnown && cfg.AutoCopy {
|
||||
triggerAutoCopy(cp, cfg, ev.Info, *mediaPath)
|
||||
}
|
||||
case disk.DiskAbsent:
|
||||
closeDiskDB()
|
||||
}
|
||||
})
|
||||
|
||||
// Open DB immediately if disk already connected at startup
|
||||
{
|
||||
info, _ := disk.Probe(*mountPath)
|
||||
if info.State == disk.DiskKnown {
|
||||
openDiskDB(info)
|
||||
}
|
||||
}
|
||||
|
||||
srv, err := api.New(api.Deps{
|
||||
Config: cfg,
|
||||
ConfigPath: *configPath,
|
||||
Watcher: w,
|
||||
Copier: cp,
|
||||
Tasks: taskStore,
|
||||
MediaPath: *mediaPath,
|
||||
MountPath: *mountPath,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("init server: %v", err)
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
go w.Run(ctx)
|
||||
|
||||
httpSrv := &http.Server{Addr: *addr, Handler: srv}
|
||||
go func() {
|
||||
log.Printf("jukebox_maker listening on %s", *addr)
|
||||
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("http: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
log.Println("shutting down…")
|
||||
shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
httpSrv.Shutdown(shutCtx)
|
||||
closeDiskDB()
|
||||
}
|
||||
|
||||
func triggerAutoCopy(cp *copier.Copier, cfg *config.Config, info disk.DiskInfo, mediaPath string) {
|
||||
var sources []string
|
||||
for _, s := range cfg.Sources {
|
||||
if s.Enabled {
|
||||
sources = append(sources, s.Path)
|
||||
}
|
||||
}
|
||||
if len(sources) == 0 {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
_, err := cp.Start(context.Background(), copier.Options{
|
||||
DiskID: info.DiskID,
|
||||
MountPath: info.MountPath,
|
||||
MediaPath: mediaPath,
|
||||
EnabledSources: sources,
|
||||
ReserveFreeGB: cfg.ReserveFreeGB,
|
||||
OverwriteMode: cfg.OverwriteMode,
|
||||
FileSelectMode: cfg.FileSelectMode,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("auto-copy: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user