+package main
+
+import (
+ "flag"
+ "golang.org/x/net/websocket"
+ "html/template"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const formatString = "2006-01-02T15:04"
+
+var config ServerConfig = NewServerConfig()
+
+// Channel that will be closed and recreated every time the playlists change
+var playlistChangeWait = make(chan bool)
+
+func main() {
+ configFlag := flag.String("c", "", "path to configuration file")
+ // TODO: support this
+ //generateFlag := flag.String("g", "", "create a template config file with specified name then exit")
+ flag.Parse()
+
+ if *configFlag == "" {
+ log.Fatal("must specify a configuration file with -c")
+ }
+ config.LoadFromFile(*configFlag)
+
+ log.Println("Hello, World! Woo broadcast time")
+ InitDatabase()
+ defer db.CloseDatabase()
+
+ InitAudioFiles(config.AudioFilesPath)
+ InitServerStatus()
+
+ http.HandleFunc("/", homePage)
+ http.HandleFunc("/login", logInPage)
+ http.HandleFunc("/logout", logOutPage)
+ http.HandleFunc("/secret", secretPage)
+
+ http.HandleFunc("/playlist/", playlistSection)
+ http.HandleFunc("/file/", fileSection)
+ http.HandleFunc("/radio/", radioSection)
+
+ http.Handle("/radiosync", websocket.Handler(RadioSync))
+ http.Handle("/websync", websocket.Handler(WebSync))
+ http.Handle("/audio-files/", http.StripPrefix("/audio-files/", http.FileServer(http.Dir(config.AudioFilesPath))))
+
+ err := http.ListenAndServe(config.BindAddress+":"+strconv.Itoa(config.Port), nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+type HomeData struct {
+ LoggedIn bool
+ Username string
+}
+
+func homePage(w http.ResponseWriter, r *http.Request) {
+ tmpl := template.Must(template.ParseFiles("templates/index.html"))
+ data := HomeData{
+ LoggedIn: true,
+ Username: "Bob",
+ }
+ tmpl.Execute(w, data)
+}
+
+func secretPage(w http.ResponseWriter, r *http.Request) {
+ user, err := currentUser(w, r)
+ if err != nil {
+ http.Redirect(w, r, "/login", http.StatusFound)
+ return
+ }
+ tmpl := template.Must(template.ParseFiles("templates/index.html"))
+ data := HomeData{
+ LoggedIn: true,
+ Username: user.username + ", you are special",
+ }
+ tmpl.Execute(w, data)
+}
+
+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"]
+ err := ""
+ if username != nil {
+ log.Println("Looks like we have a username", username[0])
+ if username[0] == "admin" && password[0] == "test" {
+ createSessionCookie(w)
+ http.Redirect(w, r, "/", http.StatusFound)
+ return
+ } else {
+ err = "Incorrect login"
+ }
+ }
+
+ data := LogInData{
+ Error: err,
+ }
+
+ tmpl := template.Must(template.ParseFiles("templates/login.html"))
+ tmpl.Execute(w, data)
+}
+
+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 PlaylistsPageData struct {
+ Playlists []Playlist
+}
+
+func playlistsPage(w http.ResponseWriter, _ *http.Request) {
+ data := PlaylistsPageData{
+ Playlists: db.GetPlaylists(),
+ }
+ tmpl := template.Must(template.ParseFiles("templates/playlists.html"))
+ err := tmpl.Execute(w, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+type RadiosPageData struct {
+ Radios []Radio
+}
+
+func radiosPage(w http.ResponseWriter, _ *http.Request) {
+ data := RadiosPageData{
+ Radios: db.GetRadios(),
+ }
+ tmpl := template.Must(template.ParseFiles("templates/radios.html"))
+ err := tmpl.Execute(w, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+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)
+ }
+ tmpl := template.Must(template.ParseFiles("templates/playlist.html"))
+ tmpl.Execute(w, data)
+}
+
+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
+ close(playlistChangeWait)
+ playlistChangeWait = make(chan bool)
+ }
+ http.Redirect(w, r, "/playlist/", 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)
+ }
+ http.Redirect(w, r, "/playlist/", 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
+ }
+ tmpl := template.Must(template.ParseFiles("templates/radio.html"))
+ tmpl.Execute(w, data)
+}
+
+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, "/radio/", 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, "/radio/", http.StatusFound)
+}
+
+type FilesPageData struct {
+ Files []FileSpec
+}
+
+func filesPage(w http.ResponseWriter, _ *http.Request) {
+ data := FilesPageData{
+ Files: files.Files(),
+ }
+ log.Println("file page data", data)
+ tmpl := template.Must(template.ParseFiles("templates/files.html"))
+ err := tmpl.Execute(w, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+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, "/file/", 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, "/file/", http.StatusFound)
+}
+
+func logOutPage(w http.ResponseWriter, r *http.Request) {
+ clearSessionCookie(w)
+ tmpl := template.Must(template.ParseFiles("templates/logout.html"))
+ tmpl.Execute(w, nil)
+}