Skip to content

Commit

Permalink
refactor: consolidate specid <-> filesystem mapping and always use ab…
Browse files Browse the repository at this point in the history
…s paths (#309)
  • Loading branch information
sentriz authored Apr 22, 2023
1 parent efe72fc commit 74de064
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 172 deletions.
126 changes: 89 additions & 37 deletions cmd/gonic/gonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package main

import (
"errors"
"flag"
"fmt"
"log"
Expand All @@ -21,38 +22,44 @@ import (

"go.senan.xyz/gonic"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/paths"
"go.senan.xyz/gonic/server"
"go.senan.xyz/gonic/server/ctrlsubsonic"
)

const (
cleanTimeDuration = 10 * time.Minute
cachePrefixAudio = "audio"
cachePrefixCovers = "covers"
)

func main() {
set := flag.NewFlagSet(gonic.Name, flag.ExitOnError)
confListenAddr := set.String("listen-addr", "0.0.0.0:4747", "listen address (optional)")

confTLSCert := set.String("tls-cert", "", "path to TLS certificate (optional)")
confTLSKey := set.String("tls-key", "", "path to TLS private key (optional)")

confPodcastPurgeAgeDays := set.Int("podcast-purge-age", 0, "age (in days) to purge podcast episodes if not accessed (optional)")
confPodcastPath := set.String("podcast-path", "", "path to podcasts")

confCachePath := set.String("cache-path", "", "path to cache")

var confMusicPaths pathAliases
set.Var(&confMusicPaths, "music-path", "path to music")

confDBPath := set.String("db-path", "gonic.db", "path to database (optional)")

confScanIntervalMins := set.Int("scan-interval", 0, "interval (in minutes) to automatically scan music (optional)")
confScanAtStart := set.Bool("scan-at-start-enabled", false, "whether to perform an initial scan at startup (optional)")
confScanWatcher := set.Bool("scan-watcher-enabled", false, "whether to watch file system for new music and rescan (optional)")

confJukeboxEnabled := set.Bool("jukebox-enabled", false, "whether the subsonic jukebox api should be enabled (optional)")
confJukeboxMPVExtraArgs := set.String("jukebox-mpv-extra-args", "", "extra command line arguments to pass to the jukebox mpv daemon (optional)")
confPodcastPurgeAgeDays := set.Int("podcast-purge-age", 0, "age (in days) to purge podcast episodes if not accessed (optional)")

confProxyPrefix := set.String("proxy-prefix", "", "url path prefix to use if behind proxy. eg '/gonic' (optional)")
confGenreSplit := set.String("genre-split", "\n", "character or string to split genre tag data on (optional)")
confHTTPLog := set.Bool("http-log", true, "http request logging (optional)")
confShowVersion := set.Bool("version", false, "show gonic version")

var confMusicPaths paths.MusicPaths
set.Var(&confMusicPaths, "music-path", "path to music")
confGenreSplit := set.String("genre-split", "\n", "character or string to split genre tag data on (optional)")

confShowVersion := set.Bool("version", false, "show gonic version")
_ = set.String("config-path", "", "path to config (optional)")

if err := ff.Parse(set, os.Args[1:],
Expand All @@ -68,40 +75,31 @@ func main() {
os.Exit(0)
}

log.Printf("starting gonic v%s\n", gonic.Version)
log.Printf("provided config\n")
set.VisitAll(func(f *flag.Flag) {
value := strings.ReplaceAll(f.Value.String(), "\n", "")
log.Printf(" %-25s %s\n", f.Name, value)
})

if len(confMusicPaths) == 0 {
log.Fatalf("please provide a music directory")
}
for _, confMusicPath := range confMusicPaths {
if _, err := os.Stat(confMusicPath.Path); os.IsNotExist(err) {
log.Fatalf("music directory %q not found", confMusicPath.Path)

var err error
for i, confMusicPath := range confMusicPaths {
if confMusicPaths[i].path, err = validatePath(confMusicPath.path); err != nil {
log.Fatalf("checking music dir %q: %v", confMusicPath.path, err)
}
}
if _, err := os.Stat(*confPodcastPath); os.IsNotExist(err) {
log.Fatal("please provide a valid podcast directory")
}

if *confCachePath == "" {
log.Fatal("please provide a cache directory")
if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil {
log.Fatalf("checking podcast directory: %v", err)
}
if *confCachePath, err = validatePath(*confCachePath); err != nil {
log.Fatalf("checking cache directory: %v", err)
}

cacheDirAudio := path.Join(*confCachePath, cachePrefixAudio)
cacheDirCovers := path.Join(*confCachePath, cachePrefixCovers)
if _, err := os.Stat(cacheDirAudio); os.IsNotExist(err) {
if err := os.MkdirAll(cacheDirAudio, os.ModePerm); err != nil {
log.Fatalf("couldn't create audio cache path: %v\n", err)
}
cacheDirAudio := path.Join(*confCachePath, "audio")
cacheDirCovers := path.Join(*confCachePath, "covers")
if err := os.MkdirAll(cacheDirAudio, os.ModePerm); err != nil {
log.Fatalf("couldn't create audio cache path: %v\n", err)
}
if _, err := os.Stat(cacheDirCovers); os.IsNotExist(err) {
if err := os.MkdirAll(cacheDirCovers, os.ModePerm); err != nil {
log.Fatalf("couldn't create covers cache path: %v\n", err)
}
if err := os.MkdirAll(cacheDirCovers, os.ModePerm); err != nil {
log.Fatalf("couldn't create covers cache path: %v\n", err)
}

dbc, err := db.New(*confDBPath, db.DefaultOptions())
Expand All @@ -111,29 +109,41 @@ func main() {
defer dbc.Close()

err = dbc.Migrate(db.MigrationContext{
OriginalMusicPath: confMusicPaths[0].Path,
OriginalMusicPath: confMusicPaths[0].path,
})
if err != nil {
log.Panicf("error migrating database: %v\n", err)
}

var musicPaths []ctrlsubsonic.MusicPath
for _, pa := range confMusicPaths {
musicPaths = append(musicPaths, ctrlsubsonic.MusicPath{Alias: pa.alias, Path: pa.path})
}

proxyPrefixExpr := regexp.MustCompile(`^\/*(.*?)\/*$`)
*confProxyPrefix = proxyPrefixExpr.ReplaceAllString(*confProxyPrefix, `/$1`)
server, err := server.New(server.Options{
DB: dbc,
MusicPaths: confMusicPaths,
CachePath: filepath.Clean(cacheDirAudio),
MusicPaths: musicPaths,
CacheAudioPath: cacheDirAudio,
CoverCachePath: cacheDirCovers,
PodcastPath: *confPodcastPath,
ProxyPrefix: *confProxyPrefix,
GenreSplit: *confGenreSplit,
PodcastPath: filepath.Clean(*confPodcastPath),
HTTPLog: *confHTTPLog,
JukeboxEnabled: *confJukeboxEnabled,
})
if err != nil {
log.Panicf("error creating server: %v\n", err)
}

log.Printf("starting gonic v%s\n", gonic.Version)
log.Printf("provided config\n")
set.VisitAll(func(f *flag.Flag) {
value := strings.ReplaceAll(f.Value.String(), "\n", "")
log.Printf(" %-25s %s\n", f.Name, value)
})

var g run.Group
g.Add(server.StartHTTP(*confListenAddr, *confTLSCert, *confTLSKey))
g.Add(server.StartSessionClean(cleanTimeDuration))
Expand All @@ -160,3 +170,45 @@ func main() {
log.Panicf("error in job: %v", err)
}
}

const pathAliasSep = "->"

type pathAliases []pathAlias
type pathAlias struct{ alias, path string }

func (pa pathAliases) String() string {
var strs []string
for _, p := range pa {
if p.alias != "" {
strs = append(strs, fmt.Sprintf("%s %s %s", p.alias, pathAliasSep, p.path))
continue
}
strs = append(strs, p.path)
}
return strings.Join(strs, ", ")
}

func (pa *pathAliases) Set(value string) error {
if name, path, ok := strings.Cut(value, pathAliasSep); ok {
*pa = append(*pa, pathAlias{alias: name, path: path})
return nil
}
*pa = append(*pa, pathAlias{path: value})
return nil
}

var errNotExists = errors.New("path does not exist, please provide one")

func validatePath(p string) (string, error) {
if p == "" {
return "", errNotExists
}
if _, err := os.Stat(p); os.IsNotExist(err) {
return "", errNotExists
}
p, err := filepath.Abs(p)
if err != nil {
return "", fmt.Errorf("make absolute: %w", err)
}
return p, nil
}
5 changes: 5 additions & 0 deletions db/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ type PodcastEpisode struct {
Filename string
Status PodcastEpisodeStatus
Error string
AbsP string `gorm:"-"` // TODO: not this. instead we need some consistent way to get the AbsPath for both tracks and podcast episodes. or just files in general
}

func (pe *PodcastEpisode) AudioLength() int { return pe.Length }
Expand All @@ -423,6 +424,10 @@ func (pe *PodcastEpisode) MIME() string {
return mime.TypeByExtension(filepath.Ext(pe.Filename))
}

func (pe *PodcastEpisode) AbsPath() string {
return pe.AbsP
}

type Bookmark struct {
ID int `gorm:"primary_key"`
User *User
Expand Down
4 changes: 1 addition & 3 deletions jukebox/jukebox.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,8 @@ func (j *Jukebox) SetPlaylist(items []string) error {
return item.Current
})

cwd, _ := os.Getwd()
currFilename, _ := filepath.Rel(cwd, current.Filename)
filteredItems, foundExistingTrack := filter(items, func(filename string) bool {
return filename != currFilename
return filename != current.Filename
})

tmp, cleanup, err := tmp()
Expand Down
61 changes: 0 additions & 61 deletions paths/paths.go

This file was deleted.

21 changes: 16 additions & 5 deletions server/ctrlsubsonic/ctrl.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package ctrlsubsonic provides HTTP handlers for subsonic api
// Package ctrlsubsonic provides HTTP handlers for subsonic API
package ctrlsubsonic

import (
Expand All @@ -10,7 +10,6 @@ import (
"net/http"

"go.senan.xyz/gonic/jukebox"
"go.senan.xyz/gonic/paths"
"go.senan.xyz/gonic/podcasts"
"go.senan.xyz/gonic/scrobble"
"go.senan.xyz/gonic/server/ctrlbase"
Expand All @@ -27,12 +26,24 @@ const (
CtxParams
)

type MusicPath struct {
Alias, Path string
}

func PathsOf(paths []MusicPath) []string {
var r []string
for _, p := range paths {
r = append(r, p.Path)
}
return r
}

type Controller struct {
*ctrlbase.Controller
CachePath string
CoverCachePath string
MusicPaths []MusicPath
PodcastsPath string
MusicPaths paths.MusicPaths
CacheAudioPath string
CoverCachePath string
Jukebox *jukebox.Jukebox
Scrobblers []scrobble.Scrobbler
Podcasts *podcasts.Podcasts
Expand Down
5 changes: 2 additions & 3 deletions server/ctrlsubsonic/ctrl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"go.senan.xyz/gonic"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/mockfs"
"go.senan.xyz/gonic/paths"
"go.senan.xyz/gonic/server/ctrlbase"
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/transcode"
Expand Down Expand Up @@ -159,9 +158,9 @@ func makec(t *testing.T, roots []string, audio bool) *Controller {
m.ScanAndClean()
m.ResetDates()

var absRoots paths.MusicPaths
var absRoots []MusicPath
for _, root := range roots {
absRoots = append(absRoots, paths.MusicPath{Alias: "", Path: filepath.Join(m.TmpDir(), root)})
absRoots = append(absRoots, MusicPath{Path: filepath.Join(m.TmpDir(), root)})
}

base := &ctrlbase.Controller{DB: m.DB()}
Expand Down
Loading

0 comments on commit 74de064

Please sign in to comment.