| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- package main
- import (
- "fmt"
- "http"
- "template"
- "os"
- "json"
- "strconv"
- "time"
- "rand"
- "unicode"
- "utf8"
- )
- type Song struct {
- Yid string
- Title string
- User string
- }
- type Playlist struct {
- Id string
- }
- var templates *template.Set
- const debug = false
- func main() {
- templates = template.SetMust(template.ParseTemplateGlob("templates/*.html"))
- rand.Seed(time.Nanoseconds())
- initDb()
- http.HandleFunc("/", home)
- http.HandleFunc("/p/", playlist)
- http.HandleFunc("/add/", add)
- http.HandleFunc("/remove/", remove)
- http.HandleFunc("/move/", move)
- http.HandleFunc("/poll/", poll)
- http.HandleFunc("/create/", create)
- err := http.ListenAndServe("localhost:8000", nil)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- }
- func renderPage(w http.ResponseWriter, page string, data interface{}) {
- if debug {
- var err os.Error
- templates, err = template.ParseTemplateGlob("templates/*.html")
- if err != nil {
- fmt.Fprintln(os.Stderr, err.String())
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- }
- }
- err := templates.Execute(w, page + ".html", data)
- if err != nil {
- fmt.Fprintln(os.Stderr, err.String())
- }
- }
- func home(w http.ResponseWriter, r *http.Request) {
- renderPage(w, "home", nil)
- go track("home", r.Header.Get("X-Forwarded-For"), "", "", "")
- }
- func playlist(w http.ResponseWriter, r *http.Request) {
- id := r.URL.Path[len("/p/"):]
- if len(id) < 8 {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return
- }
- db := <-dbPool
- defer func () {dbPool <- db}()
- count, err := queryInt(db, "SELECT COUNT(`pid`) FROM `playlist` WHERE `id` = ?", id)
- if count == 0 || err != nil {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return
- }
- p := Playlist{Id: id}
- renderPage(w, "p", p)
- go track("p", r.Header.Get("X-Forwarded-For"), id, "", "")
- }
- func add(w http.ResponseWriter, r *http.Request) {
- q := r.URL.Query()
- db := <-dbPool
- defer func () {dbPool <- db}()
- pid := getpid(db, 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
- }
- // unfortunately, MAX(`order`) returns NULL when there is nothing
- // so query for COUNT(`sid`)
- count, err := queryInt(db, "SELECT COUNT(`sid`) FROM `song` WHERE `pid` = ?", pid)
- if err != nil {
- db.Rollback()
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- }
- _, err = prepare(db,
- "INSERT INTO `song` (`pid`,`yid`,`title`,`user`,`order`) VALUES(?, ?, ?, ?, ?)",
- pid, q.Get("yid"), q.Get("title"), q.Get("user"), count)
- 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")})
- go track("add", r.Header.Get("X-Forwarded-For"), q.Get("pid"), q.Get("title"), q.Get("user"))
- }
- func remove(w http.ResponseWriter, r *http.Request) {
- q := r.URL.Query()
- db := <-dbPool
- defer func () {dbPool <- db}()
- pid := getpid(db, 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(db, "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
- }
- _, err = prepare(db, "DELETE FROM `song` WHERE `pid` = ? AND yid = ?",
- pid, q.Get("yid"))
- if err != nil {
- db.Rollback()
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- }
- _, err = prepare(db, "UPDATE `song` SET `order` = `order`-1 WHERE `order` > ? AND `pid` = ?",
- order, pid)
- 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")})
- go track("remove", r.Header.Get("X-Forwarded-For"), q.Get("pid"), "", "")
- }
- func move(w http.ResponseWriter, r *http.Request) {
- q := r.URL.Query()
- db := <-dbPool
- defer func () {dbPool <- db}()
- pid := getpid(db, 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(db, "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
- }
- query, err := prepare(db, "UPDATE `song` SET `order` = ? WHERE `order` = ? AND `pid` = ?",
- order, newOrder, pid)
- if err != nil {
- db.Rollback()
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- } else if query.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
- _, err = prepare(db, "UPDATE `song` SET `order` = ? WHERE `order` = ? AND `pid` = ? AND `yid` = ?",
- newOrder, order, pid, q.Get("yid"))
- 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 == "-1" {
- db := <-dbPool
- defer func () {dbPool <- db}()
- query, err := prepare(db,
- "SELECT `yid`,`title`,`user` FROM `playlist` JOIN `song` USING(`pid`) 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
- }
- update := getUpdates(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
- }
- w.Write([]byte("[]"))
- }
- }
- func create(w http.ResponseWriter, r *http.Request) {
- id := make([]byte, 24)
- pos := id
- for i := 0; i < 8; i++ {
- for {
- rune := rand.Intn(65536) // mysql only supports the first 65535 code points
- if unicode.IsGraphic(rune) {
- bytes := utf8.EncodeRune(pos, rune)
- pos = pos[bytes:]
- break
- }
- }
- }
- idStr := string(id)
- db := <-dbPool
- defer func () {dbPool <- db}()
- _, err := prepare(db, "INSERT INTO `playlist` (`id`) VALUES(?)", idStr)
- if err != nil {
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, "/p/" + idStr, http.StatusSeeOther)
- go track("create",r.Header.Get("X-Forwarded-For"), idStr, "", "")
- }
|