]> code.octet-stream.net Git - broadcaster/blobdiff - broadcaster-server/main.go
Simplify package names
[broadcaster] / broadcaster-server / main.go
diff --git a/broadcaster-server/main.go b/broadcaster-server/main.go
deleted file mode 100644 (file)
index 5a3cbb9..0000000
+++ /dev/null
@@ -1,537 +0,0 @@
-package main
-
-import (
-       "bufio"
-       _ "embed"
-       "flag"
-       "fmt"
-       "golang.org/x/net/websocket"
-       "html/template"
-       "io"
-       "log"
-       "net/http"
-       "os"
-       "path/filepath"
-       "strconv"
-       "strings"
-       "time"
-)
-
-const version = "v1.0.0"
-const formatString = "2006-01-02T15:04"
-
-// //go:embed templates/*
-//var content embed.FS
-var content = os.DirFS("../broadcaster-server/")
-
-var config ServerConfig = NewServerConfig()
-
-func main() {
-       configFlag := flag.String("c", "", "path to configuration file")
-       addUserFlag := flag.Bool("a", false, "interactively add an admin user then exit")
-       versionFlag := flag.Bool("v", false, "print version then exit")
-       flag.Parse()
-
-       if *versionFlag {
-               fmt.Println("Broadcaster Server", version)
-               os.Exit(0)
-       }
-       if *configFlag == "" {
-               log.Fatal("must specify a configuration file with -c")
-       }
-       config.LoadFromFile(*configFlag)
-
-       InitDatabase()
-       defer db.CloseDatabase()
-
-       if *addUserFlag {
-               scanner := bufio.NewScanner(os.Stdin)
-               fmt.Println("Enter new admin username:")
-               if !scanner.Scan() {
-                       os.Exit(1)
-               }
-               username := scanner.Text()
-               fmt.Println("Enter new admin password (will be printed in the clear):")
-               if !scanner.Scan() {
-                       os.Exit(1)
-               }
-               password := scanner.Text()
-               if username == "" || password == "" {
-                       fmt.Println("Both username and password must be specified")
-                       os.Exit(1)
-               }
-               if err := users.CreateUser(username, password, true); err != nil {
-                       log.Fatal(err)
-               }
-               os.Exit(0)
-       }
-
-       log.Println("Broadcaster Server", version, "starting up")
-       InitCommandRouter()
-       InitPlaylists()
-       InitAudioFiles(config.AudioFilesPath)
-       InitServerStatus()
-
-       // Public routes
-
-       http.HandleFunc("/login", logInPage)
-       http.Handle("/file-downloads/", http.StripPrefix("/file-downloads/", http.FileServer(http.Dir(config.AudioFilesPath))))
-
-       // Authenticated routes
-
-       http.HandleFunc("/", homePage)
-       http.HandleFunc("/logout", logOutPage)
-       http.HandleFunc("/change-password", changePasswordPage)
-
-       http.HandleFunc("/playlists/", playlistSection)
-       http.HandleFunc("/files/", fileSection)
-       http.HandleFunc("/radios/", radioSection)
-
-       http.Handle("/radio-ws", websocket.Handler(RadioSync))
-       http.Handle("/web-ws", websocket.Handler(WebSync))
-       http.HandleFunc("/stop", stopPage)
-
-       // Admin routes
-
-       err := http.ListenAndServe(config.BindAddress+":"+strconv.Itoa(config.Port), nil)
-       if err != nil {
-               log.Fatal(err)
-       }
-}
-
-type HeaderData struct {
-       SelectedMenu string
-       Username string
-}
-
-func renderHeader(w http.ResponseWriter, selectedMenu string) {
-       tmpl := template.Must(template.ParseFS(content, "templates/header.html"))
-       data := HeaderData{
-               SelectedMenu: selectedMenu,
-               Username: "username",
-       }
-       err := tmpl.Execute(w, data)
-       if err != nil {
-               log.Fatal(err)
-       }
-}
-
-func renderFooter(w http.ResponseWriter) {
-       tmpl := template.Must(template.ParseFS(content, "templates/footer.html"))
-       err := tmpl.Execute(w, nil)
-       if err != nil {
-               log.Fatal(err)
-       }
-}
-
-type HomeData struct {
-       LoggedIn bool
-       Username string
-}
-
-func homePage(w http.ResponseWriter, r *http.Request) {
-       renderHeader(w, "status")
-       tmpl := template.Must(template.ParseFS(content, "templates/index.html"))
-       data := HomeData{
-               LoggedIn: true,
-               Username: "Bob",
-       }
-       tmpl.Execute(w, data)
-       renderFooter(w)
-}
-
-type LogInData struct {
-       Error string
-}
-
-func logInPage(w http.ResponseWriter, r *http.Request) {
-       log.Println("Log in page!")
-       r.ParseForm()
-       username := r.Form["username"]
-       password := r.Form["password"]
-       errText := ""
-       if username != nil {
-               user, err := users.Authenticate(username[0], password[0])
-               if err != nil {
-                       errText = "Incorrect login"
-               } else {
-                       createSessionCookie(w, user.Username)
-                       http.Redirect(w, r, "/", http.StatusFound)
-                       return
-               }
-       }
-
-       data := LogInData{
-               Error: errText,
-       }
-       renderHeader(w, "")
-       tmpl := template.Must(template.ParseFS(content, "templates/login.html"))
-       tmpl.Execute(w, data)
-       renderFooter(w)
-}
-
-func playlistSection(w http.ResponseWriter, r *http.Request) {
-       path := strings.Split(r.URL.Path, "/")
-       if len(path) != 3 {
-               http.NotFound(w, r)
-               return
-       }
-       if path[2] == "new" {
-               editPlaylistPage(w, r, 0)
-       } else if path[2] == "submit" && r.Method == "POST" {
-               submitPlaylist(w, r)
-       } else if path[2] == "delete" && r.Method == "POST" {
-               deletePlaylist(w, r)
-       } else if path[2] == "" {
-               playlistsPage(w, r)
-       } else {
-               id, err := strconv.Atoi(path[2])
-               if err != nil {
-                       http.NotFound(w, r)
-                       return
-               }
-               editPlaylistPage(w, r, id)
-       }
-}
-
-func fileSection(w http.ResponseWriter, r *http.Request) {
-       path := strings.Split(r.URL.Path, "/")
-       if len(path) != 3 {
-               http.NotFound(w, r)
-               return
-       }
-       if path[2] == "upload" {
-               uploadFile(w, r)
-       } else if path[2] == "delete" && r.Method == "POST" {
-               deleteFile(w, r)
-       } else if path[2] == "" {
-               filesPage(w, r)
-       } else {
-               http.NotFound(w, r)
-               return
-       }
-}
-
-func radioSection(w http.ResponseWriter, r *http.Request) {
-       path := strings.Split(r.URL.Path, "/")
-       if len(path) != 3 {
-               http.NotFound(w, r)
-               return
-       }
-       if path[2] == "new" {
-               editRadioPage(w, r, 0)
-       } else if path[2] == "submit" && r.Method == "POST" {
-               submitRadio(w, r)
-       } else if path[2] == "delete" && r.Method == "POST" {
-               deleteRadio(w, r)
-       } else if path[2] == "" {
-               radiosPage(w, r)
-       } else {
-               id, err := strconv.Atoi(path[2])
-               if err != nil {
-                       http.NotFound(w, r)
-                       return
-               }
-               editRadioPage(w, r, id)
-       }
-}
-
-type ChangePasswordPageData struct {
-       Message  string
-       ShowForm bool
-}
-
-func changePasswordPage(w http.ResponseWriter, r *http.Request) {
-       user, err := currentUser(w, r)
-       if err != nil {
-               http.Redirect(w, r, "/login", http.StatusFound)
-               return
-       }
-       var data ChangePasswordPageData
-       if r.Method == "POST" {
-               err := r.ParseForm()
-               if err != nil {
-                       w.WriteHeader(http.StatusBadRequest)
-                       return
-               }
-               oldPassword := r.Form.Get("oldPassword")
-               newPassword := r.Form.Get("newPassword")
-               err = users.UpdatePassword(user.Username, oldPassword, newPassword)
-               if err != nil {
-                       data.Message = "Failed to change password: " + err.Error()
-                       data.ShowForm = true
-               } else {
-                       data.Message = "Successfully changed password"
-                       data.ShowForm = false
-                       cookie, err := r.Cookie("broadcast_session")
-                       if err == nil {
-                               log.Println("clearing other sessions for username", user.Username, "token", cookie.Value)
-                               db.ClearOtherSessions(user.Username, cookie.Value)
-                       }
-               }
-       } else {
-               data.Message = ""
-               data.ShowForm = true
-       }
-       renderHeader(w, "change-password")
-       tmpl := template.Must(template.ParseFS(content, "templates/change_password.html"))
-       err = tmpl.Execute(w, data)
-       if err != nil {
-               log.Fatal(err)
-       }
-       renderFooter(w)
-}
-
-type PlaylistsPageData struct {
-       Playlists []Playlist
-}
-
-func playlistsPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "playlists")
-       data := PlaylistsPageData{
-               Playlists: db.GetPlaylists(),
-       }
-       tmpl := template.Must(template.ParseFS(content, "templates/playlists.html"))
-       err := tmpl.Execute(w, data)
-       if err != nil {
-               log.Fatal(err)
-       }
-       renderFooter(w)
-}
-
-type RadiosPageData struct {
-       Radios []Radio
-}
-
-func radiosPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "radios")
-       data := RadiosPageData{
-               Radios: db.GetRadios(),
-       }
-       tmpl := template.Must(template.ParseFS(content, "templates/radios.html"))
-       err := tmpl.Execute(w, data)
-       if err != nil {
-               log.Fatal(err)
-       }
-       renderFooter(w)
-}
-
-type EditPlaylistPageData struct {
-       Playlist Playlist
-       Entries  []PlaylistEntry
-       Files    []string
-}
-
-func editPlaylistPage(w http.ResponseWriter, r *http.Request, id int) {
-       var data EditPlaylistPageData
-       for _, f := range files.Files() {
-               data.Files = append(data.Files, f.Name)
-       }
-       if id == 0 {
-               data.Playlist.Enabled = true
-               data.Playlist.Name = "New Playlist"
-               data.Playlist.StartTime = time.Now().Format(formatString)
-               data.Entries = append(data.Entries, PlaylistEntry{})
-       } else {
-               playlist, err := db.GetPlaylist(id)
-               if err != nil {
-                       http.NotFound(w, r)
-                       return
-               }
-               data.Playlist = playlist
-               data.Entries = db.GetEntriesForPlaylist(id)
-       }
-       renderHeader(w, "radios")
-       tmpl := template.Must(template.ParseFS(content, "templates/playlist.html"))
-       tmpl.Execute(w, data)
-       renderFooter(w)
-}
-
-func submitPlaylist(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseForm()
-       if err == nil {
-               var p Playlist
-               id, err := strconv.Atoi(r.Form.Get("playlistId"))
-               if err != nil {
-                       return
-               }
-               _, err = time.Parse(formatString, r.Form.Get("playlistStartTime"))
-               if err != nil {
-                       return
-               }
-               p.Id = id
-               p.Enabled = r.Form.Get("playlistEnabled") == "1"
-               p.Name = r.Form.Get("playlistName")
-               p.StartTime = r.Form.Get("playlistStartTime")
-
-               delays := r.Form["delaySeconds"]
-               filenames := r.Form["filename"]
-               isRelatives := r.Form["isRelative"]
-
-               entries := make([]PlaylistEntry, 0)
-               for i := range delays {
-                       var e PlaylistEntry
-                       delay, err := strconv.Atoi(delays[i])
-                       if err != nil {
-                               return
-                       }
-                       e.DelaySeconds = delay
-                       e.Position = i
-                       e.IsRelative = isRelatives[i] == "1"
-                       e.Filename = filenames[i]
-                       entries = append(entries, e)
-               }
-               cleanedEntries := make([]PlaylistEntry, 0)
-               for _, e := range entries {
-                       if e.DelaySeconds != 0 || e.Filename != "" {
-                               cleanedEntries = append(cleanedEntries, e)
-                       }
-               }
-
-               if id != 0 {
-                       db.UpdatePlaylist(p)
-               } else {
-                       id = db.CreatePlaylist(p)
-               }
-               db.SetEntriesForPlaylist(cleanedEntries, id)
-               // Notify connected radios
-               playlists.NotifyChanges()
-       }
-       http.Redirect(w, r, "/playlists/", http.StatusFound)
-}
-
-func deletePlaylist(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseForm()
-       if err == nil {
-               id, err := strconv.Atoi(r.Form.Get("playlistId"))
-               if err != nil {
-                       return
-               }
-               db.DeletePlaylist(id)
-               playlists.NotifyChanges()
-       }
-       http.Redirect(w, r, "/playlists/", http.StatusFound)
-}
-
-type EditRadioPageData struct {
-       Radio Radio
-}
-
-func editRadioPage(w http.ResponseWriter, r *http.Request, id int) {
-       var data EditRadioPageData
-       if id == 0 {
-               data.Radio.Name = "New Radio"
-               data.Radio.Token = generateSession()
-       } else {
-               radio, err := db.GetRadio(id)
-               if err != nil {
-                       http.NotFound(w, r)
-                       return
-               }
-               data.Radio = radio
-       }
-       renderHeader(w, "radios")
-       tmpl := template.Must(template.ParseFS(content, "templates/radio.html"))
-       tmpl.Execute(w, data)
-       renderFooter(w)
-}
-
-func submitRadio(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseForm()
-       if err == nil {
-               var radio Radio
-               id, err := strconv.Atoi(r.Form.Get("radioId"))
-               if err != nil {
-                       return
-               }
-               radio.Id = id
-               radio.Name = r.Form.Get("radioName")
-               radio.Token = r.Form.Get("radioToken")
-               if id != 0 {
-                       db.UpdateRadio(radio)
-               } else {
-                       db.CreateRadio(radio)
-               }
-       }
-       http.Redirect(w, r, "/radios/", http.StatusFound)
-}
-
-func deleteRadio(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseForm()
-       if err == nil {
-               id, err := strconv.Atoi(r.Form.Get("radioId"))
-               if err != nil {
-                       return
-               }
-               db.DeleteRadio(id)
-       }
-       http.Redirect(w, r, "/radios/", http.StatusFound)
-}
-
-type FilesPageData struct {
-       Files []FileSpec
-}
-
-func filesPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "files")
-       data := FilesPageData{
-               Files: files.Files(),
-       }
-       log.Println("file page data", data)
-       tmpl := template.Must(template.ParseFS(content, "templates/files.html"))
-       err := tmpl.Execute(w, data)
-       if err != nil {
-               log.Fatal(err)
-       }
-       renderFooter(w)
-}
-
-func deleteFile(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseForm()
-       if err == nil {
-               filename := r.Form.Get("filename")
-               if filename == "" {
-                       return
-               }
-               files.Delete(filename)
-       }
-       http.Redirect(w, r, "/files/", http.StatusFound)
-}
-
-func uploadFile(w http.ResponseWriter, r *http.Request) {
-       err := r.ParseMultipartForm(100 << 20)
-       file, handler, err := r.FormFile("file")
-       if err == nil {
-               path := filepath.Join(files.Path(), filepath.Base(handler.Filename))
-               f, _ := os.Create(path)
-               defer f.Close()
-               io.Copy(f, file)
-               log.Println("uploaded file to", path)
-               files.Refresh()
-       }
-       http.Redirect(w, r, "/files/", http.StatusFound)
-}
-
-func logOutPage(w http.ResponseWriter, r *http.Request) {
-       clearSessionCookie(w)
-       renderHeader(w, "")
-       tmpl := template.Must(template.ParseFS(content, "templates/logout.html"))
-       tmpl.Execute(w, nil)
-       renderFooter(w)
-}
-
-func stopPage(w http.ResponseWriter, r *http.Request) {
-       _, err := currentUser(w, r)
-       if err != nil {
-               http.Redirect(w, r, "/login", http.StatusFound)
-               return
-       }
-       r.ParseForm()
-       radioId, err := strconv.Atoi(r.Form.Get("radioId"))
-       if err != nil {
-               http.NotFound(w, r)
-               return
-       }
-       commandRouter.Stop(radioId)
-       http.Redirect(w, r, "/", http.StatusFound)
-}