@@ -1,6 +1,6 @@
|
||||
// Package main is the gonic server entrypoint
|
||||
//
|
||||
//nolint:lll // flags help strings
|
||||
//nolint:lll,gocyclo
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -16,19 +17,27 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/securecookie"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/oklog/run"
|
||||
"github.com/peterbourgon/ff"
|
||||
"github.com/sentriz/gormstore"
|
||||
|
||||
"go.senan.xyz/gonic"
|
||||
"go.senan.xyz/gonic/db"
|
||||
"go.senan.xyz/gonic/jukebox"
|
||||
"go.senan.xyz/gonic/playlist"
|
||||
"go.senan.xyz/gonic/podcasts"
|
||||
"go.senan.xyz/gonic/scanner"
|
||||
"go.senan.xyz/gonic/server"
|
||||
"go.senan.xyz/gonic/scanner/tags"
|
||||
"go.senan.xyz/gonic/scrobble"
|
||||
"go.senan.xyz/gonic/scrobble/lastfm"
|
||||
"go.senan.xyz/gonic/scrobble/listenbrainz"
|
||||
"go.senan.xyz/gonic/server/ctrladmin"
|
||||
"go.senan.xyz/gonic/server/ctrlbase"
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic"
|
||||
)
|
||||
|
||||
const (
|
||||
cleanTimeDuration = 10 * time.Minute
|
||||
"go.senan.xyz/gonic/transcode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -146,26 +155,6 @@ func main() {
|
||||
*deprecatedConfGenreSplit = "<deprecated>"
|
||||
}
|
||||
|
||||
server, err := server.New(server.Options{
|
||||
DB: dbc,
|
||||
MusicPaths: musicPaths,
|
||||
ExcludePattern: *confExcludePatterns,
|
||||
CacheAudioPath: cacheDirAudio,
|
||||
CoverCachePath: cacheDirCovers,
|
||||
PodcastPath: *confPodcastPath,
|
||||
PlaylistsPath: *confPlaylistsPath,
|
||||
ProxyPrefix: *confProxyPrefix,
|
||||
MultiValueSettings: map[scanner.Tag]scanner.MultiValueSetting{
|
||||
scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre),
|
||||
scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist),
|
||||
},
|
||||
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) {
|
||||
@@ -173,26 +162,177 @@ func main() {
|
||||
log.Printf(" %-25s %s\n", f.Name, value)
|
||||
})
|
||||
|
||||
var g run.Group
|
||||
g.Add(server.StartHTTP(*confListenAddr, *confTLSCert, *confTLSKey))
|
||||
g.Add(server.StartSessionClean(cleanTimeDuration))
|
||||
g.Add(server.StartPodcastRefresher(time.Hour))
|
||||
if *confScanIntervalMins > 0 {
|
||||
tickerDur := time.Duration(*confScanIntervalMins) * time.Minute
|
||||
g.Add(server.StartScanTicker(tickerDur))
|
||||
}
|
||||
if *confScanWatcher {
|
||||
g.Add(server.StartScanWatcher())
|
||||
tagger := &tags.TagReader{}
|
||||
scannr := scanner.New(
|
||||
ctrlsubsonic.PathsOf(musicPaths),
|
||||
dbc,
|
||||
map[scanner.Tag]scanner.MultiValueSetting{
|
||||
scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre),
|
||||
scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist),
|
||||
},
|
||||
tagger,
|
||||
*confExcludePatterns,
|
||||
)
|
||||
podcast := podcasts.New(dbc, *confPodcastPath, tagger)
|
||||
transcoder := transcode.NewCachingTranscoder(
|
||||
transcode.NewFFmpegTranscoder(),
|
||||
cacheDirAudio,
|
||||
)
|
||||
lastfmClient := lastfm.NewClient()
|
||||
playlistStore, err := playlist.NewStore(*confPlaylistsPath)
|
||||
if err != nil {
|
||||
log.Panicf("error creating playlists store: %v", err)
|
||||
}
|
||||
|
||||
var jukebx *jukebox.Jukebox
|
||||
if *confJukeboxEnabled {
|
||||
extraArgs, _ := shlex.Split(*confJukeboxMPVExtraArgs)
|
||||
g.Add(server.StartJukebox(extraArgs))
|
||||
jukebx = jukebox.New()
|
||||
}
|
||||
if *confPodcastPurgeAgeDays > 0 {
|
||||
g.Add(server.StartPodcastPurger(time.Duration(*confPodcastPurgeAgeDays) * 24 * time.Hour))
|
||||
|
||||
sessKey, err := dbc.GetSetting("session_key")
|
||||
if err != nil {
|
||||
log.Panicf("error getting session key: %v\n", err)
|
||||
}
|
||||
if sessKey == "" {
|
||||
if err := dbc.SetSetting("session_key", string(securecookie.GenerateRandomKey(32))); err != nil {
|
||||
log.Panicf("error setting session key: %v\n", err)
|
||||
}
|
||||
}
|
||||
sessDB := gormstore.New(dbc.DB, []byte(sessKey))
|
||||
sessDB.SessionOpts.HttpOnly = true
|
||||
sessDB.SessionOpts.SameSite = http.SameSiteLaxMode
|
||||
|
||||
ctrlBase := &ctrlbase.Controller{
|
||||
DB: dbc,
|
||||
PlaylistStore: playlistStore,
|
||||
ProxyPrefix: *confProxyPrefix,
|
||||
Scanner: scannr,
|
||||
}
|
||||
ctrlAdmin, err := ctrladmin.New(ctrlBase, sessDB, podcast, lastfmClient)
|
||||
if err != nil {
|
||||
log.Panicf("error creating admin controller: %v\n", err)
|
||||
}
|
||||
ctrlSubsonic := &ctrlsubsonic.Controller{
|
||||
Controller: ctrlBase,
|
||||
MusicPaths: musicPaths,
|
||||
PodcastsPath: *confPodcastPath,
|
||||
CacheAudioPath: cacheDirAudio,
|
||||
CacheCoverPath: cacheDirCovers,
|
||||
LastFMClient: lastfmClient,
|
||||
Scrobblers: []scrobble.Scrobbler{
|
||||
lastfm.NewScrobbler(dbc, lastfmClient),
|
||||
listenbrainz.NewScrobbler(),
|
||||
},
|
||||
Podcasts: podcast,
|
||||
Transcoder: transcoder,
|
||||
Jukebox: jukebx,
|
||||
}
|
||||
|
||||
mux := mux.NewRouter()
|
||||
ctrlbase.AddRoutes(ctrlBase, mux, *confHTTPLog)
|
||||
ctrladmin.AddRoutes(ctrlAdmin, mux.PathPrefix("/admin").Subrouter())
|
||||
ctrlsubsonic.AddRoutes(ctrlSubsonic, mux.PathPrefix("/rest").Subrouter())
|
||||
|
||||
var g run.Group
|
||||
g.Add(func() error {
|
||||
log.Print("starting job 'http'\n")
|
||||
server := &http.Server{
|
||||
Addr: *confListenAddr,
|
||||
Handler: mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
WriteTimeout: 80 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
if *confTLSCert != "" && *confTLSKey != "" {
|
||||
return server.ListenAndServeTLS(*confTLSCert, *confTLSKey)
|
||||
}
|
||||
return server.ListenAndServe()
|
||||
}, nil)
|
||||
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'session clean'\n")
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
for range ticker.C {
|
||||
sessDB.Cleanup()
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'podcast refresher'\n")
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
for range ticker.C {
|
||||
if err := podcast.RefreshPodcasts(); err != nil {
|
||||
log.Printf("failed to refresh some feeds: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'podcast purger'\n")
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
for range ticker.C {
|
||||
if err := podcast.PurgeOldPodcasts(time.Duration(*confPodcastPurgeAgeDays) * 24 * time.Hour); err != nil {
|
||||
log.Printf("error purging old podcasts: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
if *confScanIntervalMins > 0 {
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'scan timer'\n")
|
||||
ticker := time.NewTicker(time.Duration(*confScanIntervalMins) * time.Minute)
|
||||
for range ticker.C {
|
||||
if _, err := scannr.ScanAndClean(scanner.ScanOptions{}); err != nil {
|
||||
log.Printf("error scanning: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
if *confScanWatcher {
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'scan watcher'\n")
|
||||
return scannr.ExecuteWatch()
|
||||
}, func(_ error) {
|
||||
scannr.CancelWatch()
|
||||
})
|
||||
}
|
||||
|
||||
if jukebx != nil {
|
||||
var jukeboxTempDir string
|
||||
g.Add(func() error {
|
||||
log.Printf("starting job 'jukebox'\n")
|
||||
extraArgs, _ := shlex.Split(*confJukeboxMPVExtraArgs)
|
||||
var err error
|
||||
jukeboxTempDir, err = os.MkdirTemp("", "gonic-jukebox-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create tmp sock file: %w", err)
|
||||
}
|
||||
sockPath := filepath.Join(jukeboxTempDir, "sock")
|
||||
if err := jukebx.Start(sockPath, extraArgs); err != nil {
|
||||
return fmt.Errorf("start jukebox: %w", err)
|
||||
}
|
||||
if err := jukebx.Wait(); err != nil {
|
||||
return fmt.Errorf("start jukebox: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, func(_ error) {
|
||||
if err := jukebx.Quit(); err != nil {
|
||||
log.Printf("error quitting jukebox: %v", err)
|
||||
}
|
||||
_ = os.RemoveAll(jukeboxTempDir)
|
||||
})
|
||||
}
|
||||
|
||||
if *confScanAtStart {
|
||||
server.ScanAtStart()
|
||||
if _, err := scannr.ScanAndClean(scanner.ScanOptions{}); err != nil {
|
||||
log.Panicf("error scanning at start: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.Run(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user