]> code.octet-stream.net Git - broadcaster/blob - server/broadcaster.go
Make "previous item" default for relative playlist delay
[broadcaster] / server / broadcaster.go
1 package main
2
3 import (
4 "flag"
5 "golang.org/x/net/websocket"
6 "html/template"
7 "io"
8 "log"
9 "net/http"
10 "os"
11 "path/filepath"
12 "strconv"
13 "strings"
14 "time"
15 )
16
17 const formatString = "2006-01-02T15:04"
18
19 var config ServerConfig = NewServerConfig()
20
21 func main() {
22 configFlag := flag.String("c", "", "path to configuration file")
23 // TODO: support this
24 //generateFlag := flag.String("g", "", "create a template config file with specified name then exit")
25 flag.Parse()
26
27 if *configFlag == "" {
28 log.Fatal("must specify a configuration file with -c")
29 }
30 config.LoadFromFile(*configFlag)
31
32 log.Println("Hello, World! Woo broadcast time")
33 InitDatabase()
34 defer db.CloseDatabase()
35
36 InitPlaylists()
37 InitAudioFiles(config.AudioFilesPath)
38 InitServerStatus()
39
40 http.HandleFunc("/", homePage)
41 http.HandleFunc("/login", logInPage)
42 http.HandleFunc("/logout", logOutPage)
43 http.HandleFunc("/secret", secretPage)
44
45 http.HandleFunc("/playlist/", playlistSection)
46 http.HandleFunc("/file/", fileSection)
47 http.HandleFunc("/radio/", radioSection)
48
49 http.Handle("/radiosync", websocket.Handler(RadioSync))
50 http.Handle("/websync", websocket.Handler(WebSync))
51 http.Handle("/audio-files/", http.StripPrefix("/audio-files/", http.FileServer(http.Dir(config.AudioFilesPath))))
52
53 err := http.ListenAndServe(config.BindAddress+":"+strconv.Itoa(config.Port), nil)
54 if err != nil {
55 log.Fatal(err)
56 }
57 }
58
59 type HomeData struct {
60 LoggedIn bool
61 Username string
62 }
63
64 func homePage(w http.ResponseWriter, r *http.Request) {
65 tmpl := template.Must(template.ParseFiles("templates/index.html"))
66 data := HomeData{
67 LoggedIn: true,
68 Username: "Bob",
69 }
70 tmpl.Execute(w, data)
71 }
72
73 func secretPage(w http.ResponseWriter, r *http.Request) {
74 user, err := currentUser(w, r)
75 if err != nil {
76 http.Redirect(w, r, "/login", http.StatusFound)
77 return
78 }
79 tmpl := template.Must(template.ParseFiles("templates/index.html"))
80 data := HomeData{
81 LoggedIn: true,
82 Username: user.username + ", you are special",
83 }
84 tmpl.Execute(w, data)
85 }
86
87 type LogInData struct {
88 Error string
89 }
90
91 func logInPage(w http.ResponseWriter, r *http.Request) {
92 log.Println("Log in page!")
93 r.ParseForm()
94 username := r.Form["username"]
95 password := r.Form["password"]
96 err := ""
97 if username != nil {
98 log.Println("Looks like we have a username", username[0])
99 if username[0] == "admin" && password[0] == "test" {
100 createSessionCookie(w)
101 http.Redirect(w, r, "/", http.StatusFound)
102 return
103 } else {
104 err = "Incorrect login"
105 }
106 }
107
108 data := LogInData{
109 Error: err,
110 }
111
112 tmpl := template.Must(template.ParseFiles("templates/login.html"))
113 tmpl.Execute(w, data)
114 }
115
116 func playlistSection(w http.ResponseWriter, r *http.Request) {
117 path := strings.Split(r.URL.Path, "/")
118 if len(path) != 3 {
119 http.NotFound(w, r)
120 return
121 }
122 if path[2] == "new" {
123 editPlaylistPage(w, r, 0)
124 } else if path[2] == "submit" && r.Method == "POST" {
125 submitPlaylist(w, r)
126 } else if path[2] == "delete" && r.Method == "POST" {
127 deletePlaylist(w, r)
128 } else if path[2] == "" {
129 playlistsPage(w, r)
130 } else {
131 id, err := strconv.Atoi(path[2])
132 if err != nil {
133 http.NotFound(w, r)
134 return
135 }
136 editPlaylistPage(w, r, id)
137 }
138 }
139
140 func fileSection(w http.ResponseWriter, r *http.Request) {
141 path := strings.Split(r.URL.Path, "/")
142 if len(path) != 3 {
143 http.NotFound(w, r)
144 return
145 }
146 if path[2] == "upload" {
147 uploadFile(w, r)
148 } else if path[2] == "delete" && r.Method == "POST" {
149 deleteFile(w, r)
150 } else if path[2] == "" {
151 filesPage(w, r)
152 } else {
153 http.NotFound(w, r)
154 return
155 }
156 }
157
158 func radioSection(w http.ResponseWriter, r *http.Request) {
159 path := strings.Split(r.URL.Path, "/")
160 if len(path) != 3 {
161 http.NotFound(w, r)
162 return
163 }
164 if path[2] == "new" {
165 editRadioPage(w, r, 0)
166 } else if path[2] == "submit" && r.Method == "POST" {
167 submitRadio(w, r)
168 } else if path[2] == "delete" && r.Method == "POST" {
169 deleteRadio(w, r)
170 } else if path[2] == "" {
171 radiosPage(w, r)
172 } else {
173 id, err := strconv.Atoi(path[2])
174 if err != nil {
175 http.NotFound(w, r)
176 return
177 }
178 editRadioPage(w, r, id)
179 }
180 }
181
182 type PlaylistsPageData struct {
183 Playlists []Playlist
184 }
185
186 func playlistsPage(w http.ResponseWriter, _ *http.Request) {
187 data := PlaylistsPageData{
188 Playlists: db.GetPlaylists(),
189 }
190 tmpl := template.Must(template.ParseFiles("templates/playlists.html"))
191 err := tmpl.Execute(w, data)
192 if err != nil {
193 log.Fatal(err)
194 }
195 }
196
197 type RadiosPageData struct {
198 Radios []Radio
199 }
200
201 func radiosPage(w http.ResponseWriter, _ *http.Request) {
202 data := RadiosPageData{
203 Radios: db.GetRadios(),
204 }
205 tmpl := template.Must(template.ParseFiles("templates/radios.html"))
206 err := tmpl.Execute(w, data)
207 if err != nil {
208 log.Fatal(err)
209 }
210 }
211
212 type EditPlaylistPageData struct {
213 Playlist Playlist
214 Entries []PlaylistEntry
215 Files []string
216 }
217
218 func editPlaylistPage(w http.ResponseWriter, r *http.Request, id int) {
219 var data EditPlaylistPageData
220 for _, f := range files.Files() {
221 data.Files = append(data.Files, f.Name)
222 }
223 if id == 0 {
224 data.Playlist.Enabled = true
225 data.Playlist.Name = "New Playlist"
226 data.Playlist.StartTime = time.Now().Format(formatString)
227 data.Entries = append(data.Entries, PlaylistEntry{})
228 } else {
229 playlist, err := db.GetPlaylist(id)
230 if err != nil {
231 http.NotFound(w, r)
232 return
233 }
234 data.Playlist = playlist
235 data.Entries = db.GetEntriesForPlaylist(id)
236 }
237 tmpl := template.Must(template.ParseFiles("templates/playlist.html"))
238 tmpl.Execute(w, data)
239 }
240
241 func submitPlaylist(w http.ResponseWriter, r *http.Request) {
242 err := r.ParseForm()
243 if err == nil {
244 var p Playlist
245 id, err := strconv.Atoi(r.Form.Get("playlistId"))
246 if err != nil {
247 return
248 }
249 _, err = time.Parse(formatString, r.Form.Get("playlistStartTime"))
250 if err != nil {
251 return
252 }
253 p.Id = id
254 p.Enabled = r.Form.Get("playlistEnabled") == "1"
255 p.Name = r.Form.Get("playlistName")
256 p.StartTime = r.Form.Get("playlistStartTime")
257
258 delays := r.Form["delaySeconds"]
259 filenames := r.Form["filename"]
260 isRelatives := r.Form["isRelative"]
261
262 entries := make([]PlaylistEntry, 0)
263 for i := range delays {
264 var e PlaylistEntry
265 delay, err := strconv.Atoi(delays[i])
266 if err != nil {
267 return
268 }
269 e.DelaySeconds = delay
270 e.Position = i
271 e.IsRelative = isRelatives[i] == "1"
272 e.Filename = filenames[i]
273 entries = append(entries, e)
274 }
275 cleanedEntries := make([]PlaylistEntry, 0)
276 for _, e := range entries {
277 if e.DelaySeconds != 0 || e.Filename != "" {
278 cleanedEntries = append(cleanedEntries, e)
279 }
280 }
281
282 if id != 0 {
283 db.UpdatePlaylist(p)
284 } else {
285 id = db.CreatePlaylist(p)
286 }
287 db.SetEntriesForPlaylist(cleanedEntries, id)
288 // Notify connected radios
289 playlists.NotifyChanges()
290 }
291 http.Redirect(w, r, "/playlist/", http.StatusFound)
292 }
293
294 func deletePlaylist(w http.ResponseWriter, r *http.Request) {
295 err := r.ParseForm()
296 if err == nil {
297 id, err := strconv.Atoi(r.Form.Get("playlistId"))
298 if err != nil {
299 return
300 }
301 db.DeletePlaylist(id)
302 playlists.NotifyChanges()
303 }
304 http.Redirect(w, r, "/playlist/", http.StatusFound)
305 }
306
307 type EditRadioPageData struct {
308 Radio Radio
309 }
310
311 func editRadioPage(w http.ResponseWriter, r *http.Request, id int) {
312 var data EditRadioPageData
313 if id == 0 {
314 data.Radio.Name = "New Radio"
315 data.Radio.Token = generateSession()
316 } else {
317 radio, err := db.GetRadio(id)
318 if err != nil {
319 http.NotFound(w, r)
320 return
321 }
322 data.Radio = radio
323 }
324 tmpl := template.Must(template.ParseFiles("templates/radio.html"))
325 tmpl.Execute(w, data)
326 }
327
328 func submitRadio(w http.ResponseWriter, r *http.Request) {
329 err := r.ParseForm()
330 if err == nil {
331 var radio Radio
332 id, err := strconv.Atoi(r.Form.Get("radioId"))
333 if err != nil {
334 return
335 }
336 radio.Id = id
337 radio.Name = r.Form.Get("radioName")
338 radio.Token = r.Form.Get("radioToken")
339 if id != 0 {
340 db.UpdateRadio(radio)
341 } else {
342 db.CreateRadio(radio)
343 }
344 }
345 http.Redirect(w, r, "/radio/", http.StatusFound)
346 }
347
348 func deleteRadio(w http.ResponseWriter, r *http.Request) {
349 err := r.ParseForm()
350 if err == nil {
351 id, err := strconv.Atoi(r.Form.Get("radioId"))
352 if err != nil {
353 return
354 }
355 db.DeleteRadio(id)
356 }
357 http.Redirect(w, r, "/radio/", http.StatusFound)
358 }
359
360 type FilesPageData struct {
361 Files []FileSpec
362 }
363
364 func filesPage(w http.ResponseWriter, _ *http.Request) {
365 data := FilesPageData{
366 Files: files.Files(),
367 }
368 log.Println("file page data", data)
369 tmpl := template.Must(template.ParseFiles("templates/files.html"))
370 err := tmpl.Execute(w, data)
371 if err != nil {
372 log.Fatal(err)
373 }
374 }
375
376 func deleteFile(w http.ResponseWriter, r *http.Request) {
377 err := r.ParseForm()
378 if err == nil {
379 filename := r.Form.Get("filename")
380 if filename == "" {
381 return
382 }
383 files.Delete(filename)
384 }
385 http.Redirect(w, r, "/file/", http.StatusFound)
386 }
387
388 func uploadFile(w http.ResponseWriter, r *http.Request) {
389 err := r.ParseMultipartForm(100 << 20)
390 file, handler, err := r.FormFile("file")
391 if err == nil {
392 path := filepath.Join(files.Path(), filepath.Base(handler.Filename))
393 f, _ := os.Create(path)
394 defer f.Close()
395 io.Copy(f, file)
396 log.Println("uploaded file to", path)
397 files.Refresh()
398 }
399 http.Redirect(w, r, "/file/", http.StatusFound)
400 }
401
402 func logOutPage(w http.ResponseWriter, r *http.Request) {
403 clearSessionCookie(w)
404 tmpl := template.Must(template.ParseFiles("templates/logout.html"))
405 tmpl.Execute(w, nil)
406 }