Skip to content

Commit 070d3fd

Browse files
committed
remove a whole pile of startup indirection
1 parent 749233d commit 070d3fd

File tree

7 files changed

+363
-506
lines changed

7 files changed

+363
-506
lines changed

cmd/gonic/gonic.go

Lines changed: 179 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// Package main is the gonic server entrypoint
22
//
3-
//nolint:lll // flags help strings
3+
//nolint:lll,gocyclo
44
package main
55

66
import (
77
"errors"
88
"flag"
99
"fmt"
1010
"log"
11+
"net/http"
1112
"os"
1213
"path"
1314
"path/filepath"
@@ -16,19 +17,27 @@ import (
1617
"time"
1718

1819
"github.com/google/shlex"
20+
"github.com/gorilla/mux"
21+
"github.com/gorilla/securecookie"
1922
_ "github.com/jinzhu/gorm/dialects/sqlite"
2023
"github.com/oklog/run"
2124
"github.com/peterbourgon/ff"
25+
"github.com/sentriz/gormstore"
2226

2327
"go.senan.xyz/gonic"
2428
"go.senan.xyz/gonic/db"
29+
"go.senan.xyz/gonic/jukebox"
30+
"go.senan.xyz/gonic/playlist"
31+
"go.senan.xyz/gonic/podcasts"
2532
"go.senan.xyz/gonic/scanner"
26-
"go.senan.xyz/gonic/server"
33+
"go.senan.xyz/gonic/scanner/tags"
34+
"go.senan.xyz/gonic/scrobble"
35+
"go.senan.xyz/gonic/scrobble/lastfm"
36+
"go.senan.xyz/gonic/scrobble/listenbrainz"
37+
"go.senan.xyz/gonic/server/ctrladmin"
38+
"go.senan.xyz/gonic/server/ctrlbase"
2739
"go.senan.xyz/gonic/server/ctrlsubsonic"
28-
)
29-
30-
const (
31-
cleanTimeDuration = 10 * time.Minute
40+
"go.senan.xyz/gonic/transcode"
3241
)
3342

3443
func main() {
@@ -146,53 +155,184 @@ func main() {
146155
*deprecatedConfGenreSplit = "<deprecated>"
147156
}
148157

149-
server, err := server.New(server.Options{
150-
DB: dbc,
151-
MusicPaths: musicPaths,
152-
ExcludePattern: *confExcludePatterns,
153-
CacheAudioPath: cacheDirAudio,
154-
CoverCachePath: cacheDirCovers,
155-
PodcastPath: *confPodcastPath,
156-
PlaylistsPath: *confPlaylistsPath,
157-
ProxyPrefix: *confProxyPrefix,
158-
MultiValueSettings: map[scanner.Tag]scanner.MultiValueSetting{
159-
scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre),
160-
scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist),
161-
},
162-
HTTPLog: *confHTTPLog,
163-
JukeboxEnabled: *confJukeboxEnabled,
164-
})
165-
if err != nil {
166-
log.Panicf("error creating server: %v\n", err)
167-
}
168-
169158
log.Printf("starting gonic v%s\n", gonic.Version)
170159
log.Printf("provided config\n")
171160
set.VisitAll(func(f *flag.Flag) {
172161
value := strings.ReplaceAll(f.Value.String(), "\n", "")
173162
log.Printf(" %-25s %s\n", f.Name, value)
174163
})
175164

165+
tagger := &tags.TagReader{}
166+
scannr := scanner.New(
167+
ctrlsubsonic.PathsOf(musicPaths),
168+
dbc,
169+
map[scanner.Tag]scanner.MultiValueSetting{
170+
scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre),
171+
scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist),
172+
},
173+
tagger,
174+
*confExcludePatterns,
175+
)
176+
podcast := podcasts.New(dbc, *confPodcastPath, tagger)
177+
transcoder := transcode.NewCachingTranscoder(
178+
transcode.NewFFmpegTranscoder(),
179+
cacheDirAudio,
180+
)
181+
lastfmClient := lastfm.NewClient()
182+
playlistStore, err := playlist.NewStore(*confPlaylistsPath)
183+
if err != nil {
184+
log.Panicf("error creating playlists store: %v", err)
185+
}
186+
187+
var jukebx *jukebox.Jukebox
188+
if *confJukeboxEnabled {
189+
jukebx = jukebox.New()
190+
}
191+
192+
sessKey, err := dbc.GetSetting("session_key")
193+
if err != nil {
194+
log.Panicf("error getting session key: %v\n", err)
195+
}
196+
if sessKey == "" {
197+
if err := dbc.SetSetting("session_key", string(securecookie.GenerateRandomKey(32))); err != nil {
198+
log.Panicf("error setting session key: %v\n", err)
199+
}
200+
}
201+
sessDB := gormstore.New(dbc.DB, []byte(sessKey))
202+
sessDB.SessionOpts.HttpOnly = true
203+
sessDB.SessionOpts.SameSite = http.SameSiteLaxMode
204+
205+
ctrlBase := &ctrlbase.Controller{
206+
DB: dbc,
207+
PlaylistStore: playlistStore,
208+
ProxyPrefix: *confProxyPrefix,
209+
Scanner: scannr,
210+
}
211+
ctrlAdmin, err := ctrladmin.New(ctrlBase, sessDB, podcast, lastfmClient)
212+
if err != nil {
213+
log.Panicf("error creating admin controller: %v\n", err)
214+
}
215+
ctrlSubsonic := &ctrlsubsonic.Controller{
216+
Controller: ctrlBase,
217+
MusicPaths: musicPaths,
218+
PodcastsPath: *confPodcastPath,
219+
CacheAudioPath: cacheDirAudio,
220+
CacheCoverPath: cacheDirCovers,
221+
LastFMClient: lastfmClient,
222+
Scrobblers: []scrobble.Scrobbler{
223+
lastfm.NewScrobbler(dbc, lastfmClient),
224+
listenbrainz.NewScrobbler(),
225+
},
226+
Podcasts: podcast,
227+
Transcoder: transcoder,
228+
Jukebox: jukebx,
229+
}
230+
231+
mux := mux.NewRouter()
232+
ctrlbase.AddRoutes(ctrlBase, mux, *confHTTPLog)
233+
ctrladmin.AddRoutes(ctrlAdmin, mux.PathPrefix("/admin").Subrouter())
234+
ctrlsubsonic.AddRoutes(ctrlSubsonic, mux.PathPrefix("/rest").Subrouter())
235+
176236
var g run.Group
177-
g.Add(server.StartHTTP(*confListenAddr, *confTLSCert, *confTLSKey))
178-
g.Add(server.StartSessionClean(cleanTimeDuration))
179-
g.Add(server.StartPodcastRefresher(time.Hour))
237+
g.Add(func() error {
238+
log.Print("starting job 'http'\n")
239+
server := &http.Server{
240+
Addr: *confListenAddr,
241+
Handler: mux,
242+
ReadTimeout: 5 * time.Second,
243+
ReadHeaderTimeout: 5 * time.Second,
244+
WriteTimeout: 80 * time.Second,
245+
IdleTimeout: 60 * time.Second,
246+
}
247+
if *confTLSCert != "" && *confTLSKey != "" {
248+
return server.ListenAndServeTLS(*confTLSCert, *confTLSKey)
249+
}
250+
return server.ListenAndServe()
251+
}, nil)
252+
253+
g.Add(func() error {
254+
log.Printf("starting job 'session clean'\n")
255+
ticker := time.NewTicker(10 * time.Minute)
256+
for range ticker.C {
257+
sessDB.Cleanup()
258+
}
259+
return nil
260+
}, nil)
261+
262+
g.Add(func() error {
263+
log.Printf("starting job 'podcast refresher'\n")
264+
ticker := time.NewTicker(time.Hour)
265+
for range ticker.C {
266+
if err := podcast.RefreshPodcasts(); err != nil {
267+
log.Printf("failed to refresh some feeds: %s", err)
268+
}
269+
}
270+
return nil
271+
}, nil)
272+
273+
g.Add(func() error {
274+
log.Printf("starting job 'podcast purger'\n")
275+
ticker := time.NewTicker(24 * time.Hour)
276+
for range ticker.C {
277+
if err := podcast.PurgeOldPodcasts(time.Duration(*confPodcastPurgeAgeDays) * 24 * time.Hour); err != nil {
278+
log.Printf("error purging old podcasts: %v", err)
279+
}
280+
}
281+
return nil
282+
}, nil)
283+
180284
if *confScanIntervalMins > 0 {
181-
tickerDur := time.Duration(*confScanIntervalMins) * time.Minute
182-
g.Add(server.StartScanTicker(tickerDur))
285+
g.Add(func() error {
286+
log.Printf("starting job 'scan timer'\n")
287+
ticker := time.NewTicker(time.Duration(*confScanIntervalMins) * time.Minute)
288+
for range ticker.C {
289+
if _, err := scannr.ScanAndClean(scanner.ScanOptions{}); err != nil {
290+
log.Printf("error scanning: %v", err)
291+
}
292+
}
293+
return nil
294+
}, nil)
183295
}
296+
184297
if *confScanWatcher {
185-
g.Add(server.StartScanWatcher())
186-
}
187-
if *confJukeboxEnabled {
188-
extraArgs, _ := shlex.Split(*confJukeboxMPVExtraArgs)
189-
g.Add(server.StartJukebox(extraArgs))
298+
g.Add(func() error {
299+
log.Printf("starting job 'scan watcher'\n")
300+
return scannr.ExecuteWatch()
301+
}, func(_ error) {
302+
scannr.CancelWatch()
303+
})
190304
}
191-
if *confPodcastPurgeAgeDays > 0 {
192-
g.Add(server.StartPodcastPurger(time.Duration(*confPodcastPurgeAgeDays) * 24 * time.Hour))
305+
306+
if jukebx != nil {
307+
var jukeboxTempDir string
308+
g.Add(func() error {
309+
log.Printf("starting job 'jukebox'\n")
310+
extraArgs, _ := shlex.Split(*confJukeboxMPVExtraArgs)
311+
var err error
312+
jukeboxTempDir, err = os.MkdirTemp("", "gonic-jukebox-*")
313+
if err != nil {
314+
return fmt.Errorf("create tmp sock file: %w", err)
315+
}
316+
sockPath := filepath.Join(jukeboxTempDir, "sock")
317+
if err := jukebx.Start(sockPath, extraArgs); err != nil {
318+
return fmt.Errorf("start jukebox: %w", err)
319+
}
320+
if err := jukebx.Wait(); err != nil {
321+
return fmt.Errorf("start jukebox: %w", err)
322+
}
323+
return nil
324+
}, func(_ error) {
325+
if err := jukebx.Quit(); err != nil {
326+
log.Printf("error quitting jukebox: %v", err)
327+
}
328+
_ = os.RemoveAll(jukeboxTempDir)
329+
})
193330
}
331+
194332
if *confScanAtStart {
195-
server.ScanAtStart()
333+
if _, err := scannr.ScanAndClean(scanner.ScanOptions{}); err != nil {
334+
log.Panicf("error scanning at start: %v\n", err)
335+
}
196336
}
197337

198338
if err := g.Run(); err != nil {

server/ctrladmin/routes.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ctrladmin
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gorilla/mux"
7+
"go.senan.xyz/gonic/server/ctrladmin/adminui"
8+
)
9+
10+
func AddRoutes(c *Controller, r *mux.Router) {
11+
// public routes (creates session)
12+
r.Use(c.WithSession)
13+
r.Handle("/login", c.H(c.ServeLogin))
14+
r.Handle("/login_do", c.HR(c.ServeLoginDo)) // "raw" handler, updates session
15+
16+
staticHandler := http.StripPrefix("/admin", http.FileServer(http.FS(adminui.StaticFS)))
17+
r.PathPrefix("/static").Handler(staticHandler)
18+
19+
// user routes (if session is valid)
20+
routUser := r.NewRoute().Subrouter()
21+
routUser.Use(c.WithUserSession)
22+
routUser.Handle("/logout", c.HR(c.ServeLogout)) // "raw" handler, updates session
23+
routUser.Handle("/home", c.H(c.ServeHome))
24+
routUser.Handle("/change_username", c.H(c.ServeChangeUsername))
25+
routUser.Handle("/change_username_do", c.H(c.ServeChangeUsernameDo))
26+
routUser.Handle("/change_password", c.H(c.ServeChangePassword))
27+
routUser.Handle("/change_password_do", c.H(c.ServeChangePasswordDo))
28+
routUser.Handle("/change_avatar", c.H(c.ServeChangeAvatar))
29+
routUser.Handle("/change_avatar_do", c.H(c.ServeChangeAvatarDo))
30+
routUser.Handle("/delete_avatar_do", c.H(c.ServeDeleteAvatarDo))
31+
routUser.Handle("/delete_user", c.H(c.ServeDeleteUser))
32+
routUser.Handle("/delete_user_do", c.H(c.ServeDeleteUserDo))
33+
routUser.Handle("/link_lastfm_do", c.H(c.ServeLinkLastFMDo))
34+
routUser.Handle("/unlink_lastfm_do", c.H(c.ServeUnlinkLastFMDo))
35+
routUser.Handle("/link_listenbrainz_do", c.H(c.ServeLinkListenBrainzDo))
36+
routUser.Handle("/unlink_listenbrainz_do", c.H(c.ServeUnlinkListenBrainzDo))
37+
routUser.Handle("/create_transcode_pref_do", c.H(c.ServeCreateTranscodePrefDo))
38+
routUser.Handle("/delete_transcode_pref_do", c.H(c.ServeDeleteTranscodePrefDo))
39+
40+
// admin routes (if session is valid, and is admin)
41+
routAdmin := routUser.NewRoute().Subrouter()
42+
routAdmin.Use(c.WithAdminSession)
43+
routAdmin.Handle("/create_user", c.H(c.ServeCreateUser))
44+
routAdmin.Handle("/create_user_do", c.H(c.ServeCreateUserDo))
45+
routAdmin.Handle("/update_lastfm_api_key", c.H(c.ServeUpdateLastFMAPIKey))
46+
routAdmin.Handle("/update_lastfm_api_key_do", c.H(c.ServeUpdateLastFMAPIKeyDo))
47+
routAdmin.Handle("/start_scan_inc_do", c.H(c.ServeStartScanIncDo))
48+
routAdmin.Handle("/start_scan_full_do", c.H(c.ServeStartScanFullDo))
49+
routAdmin.Handle("/add_podcast_do", c.H(c.ServePodcastAddDo))
50+
routAdmin.Handle("/delete_podcast_do", c.H(c.ServePodcastDeleteDo))
51+
routAdmin.Handle("/download_podcast_do", c.H(c.ServePodcastDownloadDo))
52+
routAdmin.Handle("/update_podcast_do", c.H(c.ServePodcastUpdateDo))
53+
routAdmin.Handle("/add_internet_radio_station_do", c.H(c.ServeInternetRadioStationAddDo))
54+
routAdmin.Handle("/delete_internet_radio_station_do", c.H(c.ServeInternetRadioStationDeleteDo))
55+
routAdmin.Handle("/update_internet_radio_station_do", c.H(c.ServeInternetRadioStationUpdateDo))
56+
57+
// middlewares should be run for not found handler
58+
// https://github.com/gorilla/mux/issues/416
59+
notFoundHandler := c.H(c.ServeNotFound)
60+
notFoundRoute := r.NewRoute().Handler(notFoundHandler)
61+
r.NotFoundHandler = notFoundRoute.GetHandler()
62+
}

server/ctrlbase/routes.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package ctrlbase
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/gorilla/handlers"
8+
"github.com/gorilla/mux"
9+
)
10+
11+
func AddRoutes(c *Controller, r *mux.Router, logHTTP bool) {
12+
if logHTTP {
13+
r.Use(c.WithLogging)
14+
}
15+
r.Use(c.WithCORS)
16+
r.Use(handlers.RecoveryHandler(handlers.PrintRecoveryStack(true)))
17+
18+
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
19+
adminHome := c.Path("/admin/home")
20+
http.Redirect(w, r, adminHome, http.StatusSeeOther)
21+
})
22+
// misc subsonic routes without /rest prefix
23+
r.HandleFunc("/settings.view", func(w http.ResponseWriter, r *http.Request) {
24+
adminHome := c.Path("/admin/home")
25+
http.Redirect(w, r, adminHome, http.StatusSeeOther)
26+
})
27+
r.HandleFunc("/musicFolderSettings.view", func(w http.ResponseWriter, r *http.Request) {
28+
restScan := c.Path(fmt.Sprintf("/rest/startScan.view?%s", r.URL.Query().Encode()))
29+
http.Redirect(w, r, restScan, http.StatusSeeOther)
30+
})
31+
r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
32+
fmt.Fprint(w, "OK")
33+
})
34+
}

server/ctrlsubsonic/ctrl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Controller struct {
4444
MusicPaths []MusicPath
4545
PodcastsPath string
4646
CacheAudioPath string
47-
CoverCachePath string
47+
CacheCoverPath string
4848
Jukebox *jukebox.Jukebox
4949
Scrobblers []scrobble.Scrobbler
5050
Podcasts *podcasts.Podcasts

server/ctrlsubsonic/handlers_raw.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func (c *Controller) ServeGetCoverArt(w http.ResponseWriter, r *http.Request) *s
229229
}
230230
size := params.GetOrInt("size", coverDefaultSize)
231231
cachePath := path.Join(
232-
c.CoverCachePath,
232+
c.CacheCoverPath,
233233
fmt.Sprintf("%s-%d.%s", id.String(), size, coverCacheFormat),
234234
)
235235
_, err = os.Stat(cachePath)

0 commit comments

Comments
 (0)