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