diff --git a/cmd/dwelling-radio/main.go b/cmd/dwelling-radio/main.go index 5ae1fb7..6a7b2e9 100644 --- a/cmd/dwelling-radio/main.go +++ b/cmd/dwelling-radio/main.go @@ -1,12 +1,14 @@ package main import ( + "context" ihttp "dwelling-radio/internal/http" "dwelling-radio/internal/radio" + sqlite_stats "dwelling-radio/internal/statistics/db/sqlite" + "dwelling-radio/pkg/utils" "dwelling-radio/web" "flag" "fmt" - "io/fs" "log" "net/http" "os" @@ -17,12 +19,12 @@ import ( ) var ( - listenAddress = flag.String("listen", "/var/run/dwelling-radio/sock", "listen address (ip:port|unix_path)") - filelistPath = flag.String("filelist", "/mnt/data/appdata/radio/filelist.html", "path to a filelist.html file") - playlist = flag.String("playlist", "", "path to a playlist") - fallbackSong = flag.String("fallback-song", "", "path to a fallback song") - mostListenedSongPath = flag.String("mls-file", "/mnt/data/appdata/radio/mostlistenedsong", "path to a file that stores info about the most listened song") - songListLen = flag.Int("list-length", 10, "number of songs to show in last N songs table") + listenAddress = flag.String("listen", "/var/run/dwelling-radio/sock", "listen address (ip:port|unix_path)") + filelistPath = flag.String("filelist", "/mnt/data/appdata/radio/filelist.html", "path to a filelist.html file") + playlistPath = flag.String("playlist", "", "path to a playlist") + fallbackSong = flag.String("fallback-song", "", "path to a fallback song") + statisticsDbPath = flag.String("db", "/mnt/data/appdata/radio/statistics.db3", "path to a statistics database") + songListLen = flag.Int64("list-length", 10, "number of songs to show in last N songs table") showVersion = flag.Bool("v", false, "show version") ) @@ -38,35 +40,52 @@ func main() { return } - var mostListenedSong radio.MostListenedSong - if data, err := os.ReadFile(*mostListenedSongPath); err == nil { - if err := mostListenedSong.Load(data); err != nil { - log.Fatalln(err) - } + stats, err := sqlite_stats.New(*statisticsDbPath) + if err != nil { + log.Fatalln("Statistics:", err) } + defer stats.Close() - songList := radio.NewSongList(*songListLen) + currentSong := radio.Song{} lstnrs := radio.NewListenerCounter() - plylst, err := radio.NewPlaylist(*playlist, true) + + plylst, err := radio.NewPlaylist(*playlistPath, true) if err != nil { log.Fatalln(err) } - hand := ihttp.NewHandlers(*filelistPath, songList, lstnrs, &mostListenedSong) r := httpr.New() - r.Handler(http.MethodGet, "/", hand.Index) + r.Handler(http.MethodGet, "/", func(w http.ResponseWriter, r *http.Request) { + lst, err := stats.LastNSongs(*songListLen) + if err != nil { + log.Printf("Failed to fetch last N songs: %s\n", err) + } - r.Handler(http.MethodGet, "/playlist", ihttp.ServeAsset("playlist.m3u", "", "radio.arav.su.m3u")) - r.Handler(http.MethodGet, "/filelist", hand.Filelist) + web.Index(¤tSong, lst, *songListLen, lstnrs, r).Render(context.Background(), w) + }) - r.Handler(http.MethodGet, "/robots.txt", ihttp.ServeAsset("robots.txt", "text/plain", "")) - r.Handler(http.MethodGet, "/sitemap.xml", ihttp.ServeAsset("sitemap.xml", "application/xml", "")) - r.Handler(http.MethodGet, "/favicon.svg", ihttp.ServeAsset("favicon.svg", "image/svg", "")) + r.Handler(http.MethodGet, "/filelist", func(w http.ResponseWriter, r *http.Request) { + if *filelistPath == "" { + http.Error(w, "no filelist", http.StatusNotFound) + return + } + + w.Header().Add("Content-Type", "text/html") + w.Header().Add("Link", "<"+utils.Site(r.Host)+"/filelist>; rel=\"canonical\"") + data, _ := os.ReadFile(*filelistPath) + w.Write(data) + }) + + r.Handler(http.MethodGet, "/playlist", web.ServeAsset("playlist.m3u", "", "radio.arav.su.m3u")) + + r.Handler(http.MethodGet, "/robots.txt", web.ServeAsset("robots.txt", "text/plain", "")) + r.Handler(http.MethodGet, "/sitemap.xml", web.ServeAsset("sitemap.xml", "application/xml", "")) + r.Handler(http.MethodGet, "/favicon.svg", web.ServeAsset("favicon.svg", "image/svg", "")) r.ServeStatic("/assets/*filepath", web.Assets()) - djh := ihttp.NewDJHandlers(lstnrs, plylst, songList, &mostListenedSong, *fallbackSong) + djh := ihttp.NewDJHandlers(lstnrs, plylst, stats, ¤tSong, *songListLen, *fallbackSong) s := r.Sub("/api") @@ -81,14 +100,6 @@ func main() { } defer func() { - fileData := mostListenedSong.Store() - if fileData != nil { - err := os.WriteFile(*mostListenedSongPath, fileData, fs.ModePerm) - if err != nil { - log.Println("failed to store a most listened song:", err) - } - } - if err := srv.Stop(); err != nil { log.Fatalln(err) } @@ -100,6 +111,11 @@ func main() { for { switch <-sysSignal { case os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT, syscall.SIGSEGV: + if currentSong.Artist != "" { + if err := stats.Add(¤tSong); err != nil { + log.Println("failed to save a current song during a shutdown:", err) + } + } return case syscall.SIGHUP: if err := plylst.Reload(); err != nil {