package main import ( "fmt" "http" "exp/template" "os" "json" "strconv" "time" ) type Song struct { Yid string Title string User string } type Playlist struct { Id string } var templates map[string]*template.Template const debug = true func home(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to Audio Axis!") } func playlist(w http.ResponseWriter, r *http.Request) { id := r.URL.Path[len("/p/"):] if len(id) != 8 { http.Redirect(w, r, "/", 303) return } playlist := Playlist{Id: id} if debug { t, err := template.ParseFile("templates/p.html") if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } err = t.Execute(w, playlist) if err != nil { w.Write([]byte(err.String())) fmt.Fprintln(os.Stderr, err.String()) return } } else { err := templates["p"].Execute(w, playlist) if err != nil { fmt.Fprintln(os.Stderr, err.String()) } } } func add(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() pid := getpid(q.Get("pid")) if pid == -1 { http.Error(w, "invalid pid", http.StatusBadRequest) return } err := db.Start() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } maxOrder, err := queryInt("SELECT MAX(`order`) FROM `song` WHERE pid = ?", pid) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } sql := "INSERT INTO `song` (`pid`,`yid`,`title`,`user`,`order`) VALUES(%d,'%s','%s','%s','%d')" sql = fmt.Sprintf(sql, pid, db.Escape(q.Get("yid")), db.Escape(q.Get("title")), db.Escape(q.Get("user")), maxOrder + 1) err = execute(sql) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } err = db.Commit() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } w.Write([]byte("1")) addUpdate(pid, addAction, &Song{Yid: q.Get("yid"), Title: q.Get("title"), User: q.Get("user")}) } func remove(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() pid := getpid(q.Get("pid")) if pid == -1 { http.Error(w, "invalid pid", http.StatusBadRequest) return } err := db.Start() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } order, err := queryInt("SELECT `order` FROM `song` WHERE `yid` = ? AND `pid` = ?", q.Get("yid"), pid) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } sql := "DELETE FROM `song` WHERE `pid` = %d AND yid = '%s'" sql = fmt.Sprintf(sql, pid, db.Escape(q.Get("yid"))) err = execute(sql) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } sql = "UPDATE `song` SET `order` = `order`-1 WHERE `order` > %d AND `pid` = %d" sql = fmt.Sprintf(sql, order, pid) err = execute(sql) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } err = db.Commit() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } w.Write([]byte("1")) addUpdate(pid, removeAction, &Song{Yid: q.Get("yid")}) } func move(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() pid := getpid(q.Get("pid")) if pid == -1 { http.Error(w, "invalid pid", http.StatusBadRequest) return } direction, err := strconv.Atoui(q.Get("direction")) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } err = db.Start() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } order, err := queryInt("SELECT `order` FROM `song` WHERE `yid` = ? AND `pid` = ?", q.Get("yid"), pid) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } newOrder := order if direction == moveUpAction && order > 0 { newOrder-- } else if direction == moveDownAction { newOrder++ } else { db.Rollback() http.Error(w, "invalid direction or cannot move up", http.StatusBadRequest) return } sql := "UPDATE `song` SET `order` = %d WHERE `order` = %d AND pid = %d" sql = fmt.Sprintf(sql, order, newOrder, pid) err = execute(sql) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } else if db.AffectedRows != 1 { db.Rollback() http.Error(w, "invalid direction for this song", http.StatusBadRequest) return } // there are now two songs with that order, so also check yid sql = "UPDATE `song` SET `order` = %d WHERE `order` = %d AND pid = %d AND yid = '%s'" sql = fmt.Sprintf(sql, newOrder, order, pid, q.Get("yid")) err = db.Query(sql) if err != nil { db.Rollback() http.Error(w, err.String(), http.StatusInternalServerError) return } err = db.Commit() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } w.Write([]byte("1")) addUpdate(pid, direction, &Song{Yid: q.Get("yid")}) } func poll(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() timestamp := q.Get("timestamp") if timestamp == "0" { query, err := prepare( "SELECT `yid`,`title`,`user` FROM `playlist` JOIN `song` WHERE `id` = ? ORDER BY `order` ASC", q.Get("pid")) updates := make([]Update, 0, 2) for { song := new(Song) query.BindResult(&song.Yid, &song.Title, &song.User) eof, err := query.Fetch() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } if eof { break } updates = append(updates, Update{Song: song, Action: addAction, Timestamp: time.Nanoseconds()}) } err = query.FreeResult() if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } output, err := json.MarshalForHTML(updates) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } w.Write(output) } else { timestamp, err := strconv.Atoi64(q.Get("timestamp")) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } var update *Update for i := 0; i < 30; i++ { update = getUpdates(getpid(q.Get("pid")), timestamp) if update != nil { w.Write([]byte("[")) for update != nil { output, err := json.MarshalForHTML(update) if err == nil { w.Write(output) } update = update.Next if update != nil { w.Write([]byte(",")) } } w.Write([]byte("]")) return } time.Sleep(1e9) // 1 second } w.Write([]byte("[]")) } } func main() { templates = make(map[string]*template.Template) for _, path := range []string{"p"} { t, err := template.ParseFile("templates/" + path + ".html") if err != nil { fmt.Println(err) return } templates[path] = t } initDb() http.HandleFunc("/", home) http.HandleFunc("/p/", playlist) http.HandleFunc("/add/", add) http.HandleFunc("/remove/", remove) http.HandleFunc("/move/", move) http.HandleFunc("/poll/", poll) err := http.ListenAndServe("localhost:8000", nil) if err != nil { fmt.Println(err) os.Exit(1) } }