main.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package main
  2. import (
  3. "fmt"
  4. "http"
  5. "exp/template"
  6. "os"
  7. "json"
  8. "strconv"
  9. "time"
  10. )
  11. type Song struct {
  12. Yid string
  13. Title string
  14. User string
  15. }
  16. type Playlist struct {
  17. Id string
  18. }
  19. var templates *template.Set
  20. const debug = true
  21. func main() {
  22. templates = template.SetMust(template.ParseTemplateFiles("templates/*.html"))
  23. initDb()
  24. http.HandleFunc("/", home)
  25. http.HandleFunc("/p/", playlist)
  26. http.HandleFunc("/add/", add)
  27. http.HandleFunc("/remove/", remove)
  28. http.HandleFunc("/move/", move)
  29. http.HandleFunc("/poll/", poll)
  30. err := http.ListenAndServe("localhost:8000", nil)
  31. if err != nil {
  32. fmt.Println(err)
  33. os.Exit(1)
  34. }
  35. }
  36. func renderPage(w http.ResponseWriter, page string, data interface{}) {
  37. if debug {
  38. var err os.Error
  39. templates, err = template.ParseTemplateFiles("templates/*.html")
  40. if err != nil {
  41. fmt.Fprintln(os.Stderr, err.String())
  42. http.Error(w, err.String(), http.StatusInternalServerError)
  43. return
  44. }
  45. }
  46. err := templates.Execute(w, page + ".html", data)
  47. if err != nil {
  48. fmt.Fprintln(os.Stderr, err.String())
  49. }
  50. }
  51. func home(w http.ResponseWriter, r *http.Request) {
  52. renderPage(w, "home", nil)
  53. }
  54. func playlist(w http.ResponseWriter, r *http.Request) {
  55. id := r.URL.Path[len("/p/"):]
  56. if len(id) != 8 {
  57. http.Redirect(w, r, "/", 303)
  58. return
  59. }
  60. p := Playlist{Id: id}
  61. renderPage(w, "p", p)
  62. }
  63. func add(w http.ResponseWriter, r *http.Request) {
  64. q := r.URL.Query()
  65. db := <-dbPool
  66. defer func () {dbPool <- db}()
  67. pid := getpid(db, q.Get("pid"))
  68. if pid == -1 {
  69. http.Error(w, "invalid pid", http.StatusBadRequest)
  70. return
  71. }
  72. err := db.Start()
  73. if err != nil {
  74. http.Error(w, err.String(), http.StatusInternalServerError)
  75. return
  76. }
  77. // unfortunately, MAX(`order`) returns NULL when there is nothing
  78. // so query for COUNT(`sid`)
  79. count, err := queryInt(db, "SELECT COUNT(`sid`) FROM `song` WHERE `pid` = ?", pid)
  80. if err != nil {
  81. db.Rollback()
  82. http.Error(w, err.String(), http.StatusInternalServerError)
  83. return
  84. }
  85. _, err = prepare(db,
  86. "INSERT INTO `song` (`pid`,`yid`,`title`,`user`,`order`) VALUES(?, ?, ?, ?, ?)",
  87. pid, q.Get("yid"), q.Get("title"), q.Get("user"), count)
  88. if err != nil {
  89. db.Rollback()
  90. http.Error(w, err.String(), http.StatusInternalServerError)
  91. return
  92. }
  93. err = db.Commit()
  94. if err != nil {
  95. http.Error(w, err.String(), http.StatusInternalServerError)
  96. return
  97. }
  98. w.Write([]byte("1"))
  99. addUpdate(pid, addAction,
  100. &Song{Yid: q.Get("yid"), Title: q.Get("title"), User: q.Get("user")})
  101. }
  102. func remove(w http.ResponseWriter, r *http.Request) {
  103. q := r.URL.Query()
  104. db := <-dbPool
  105. defer func () {dbPool <- db}()
  106. pid := getpid(db, q.Get("pid"))
  107. if pid == -1 {
  108. http.Error(w, "invalid pid", http.StatusBadRequest)
  109. return
  110. }
  111. err := db.Start()
  112. if err != nil {
  113. http.Error(w, err.String(), http.StatusInternalServerError)
  114. return
  115. }
  116. order, err := queryInt(db, "SELECT `order` FROM `song` WHERE `yid` = ? AND `pid` = ?",
  117. q.Get("yid"), pid)
  118. if err != nil {
  119. db.Rollback()
  120. http.Error(w, err.String(), http.StatusInternalServerError)
  121. return
  122. }
  123. _, err = prepare(db, "DELETE FROM `song` WHERE `pid` = ? AND yid = ?",
  124. pid, q.Get("yid"))
  125. if err != nil {
  126. db.Rollback()
  127. http.Error(w, err.String(), http.StatusInternalServerError)
  128. return
  129. }
  130. _, err = prepare(db, "UPDATE `song` SET `order` = `order`-1 WHERE `order` > ? AND `pid` = ?",
  131. order, pid)
  132. if err != nil {
  133. db.Rollback()
  134. http.Error(w, err.String(), http.StatusInternalServerError)
  135. return
  136. }
  137. err = db.Commit()
  138. if err != nil {
  139. http.Error(w, err.String(), http.StatusInternalServerError)
  140. return
  141. }
  142. w.Write([]byte("1"))
  143. addUpdate(pid, removeAction, &Song{Yid: q.Get("yid")})
  144. }
  145. func move(w http.ResponseWriter, r *http.Request) {
  146. q := r.URL.Query()
  147. db := <-dbPool
  148. defer func () {dbPool <- db}()
  149. pid := getpid(db, q.Get("pid"))
  150. if pid == -1 {
  151. http.Error(w, "invalid pid", http.StatusBadRequest)
  152. return
  153. }
  154. direction, err := strconv.Atoui(q.Get("direction"))
  155. if err != nil {
  156. http.Error(w, err.String(), http.StatusInternalServerError)
  157. return
  158. }
  159. err = db.Start()
  160. if err != nil {
  161. http.Error(w, err.String(), http.StatusInternalServerError)
  162. return
  163. }
  164. order, err := queryInt(db, "SELECT `order` FROM `song` WHERE `yid` = ? AND `pid` = ?",
  165. q.Get("yid"), pid)
  166. if err != nil {
  167. db.Rollback()
  168. http.Error(w, err.String(), http.StatusInternalServerError)
  169. return
  170. }
  171. newOrder := order
  172. if direction == moveUpAction && order > 0 {
  173. newOrder--
  174. } else if direction == moveDownAction {
  175. newOrder++
  176. } else {
  177. db.Rollback()
  178. http.Error(w, "invalid direction or cannot move up", http.StatusBadRequest)
  179. return
  180. }
  181. query, err := prepare(db, "UPDATE `song` SET `order` = ? WHERE `order` = ? AND `pid` = ?",
  182. order, newOrder, pid)
  183. if err != nil {
  184. db.Rollback()
  185. http.Error(w, err.String(), http.StatusInternalServerError)
  186. return
  187. } else if query.AffectedRows != 1 {
  188. db.Rollback()
  189. http.Error(w, "invalid direction for this song", http.StatusBadRequest)
  190. return
  191. }
  192. // there are now two songs with that order, so also check yid
  193. _, err = prepare(db, "UPDATE `song` SET `order` = ? WHERE `order` = ? AND `pid` = ? AND `yid` = ?",
  194. newOrder, order, pid, q.Get("yid"))
  195. if err != nil {
  196. db.Rollback()
  197. http.Error(w, err.String(), http.StatusInternalServerError)
  198. return
  199. }
  200. err = db.Commit()
  201. if err != nil {
  202. http.Error(w, err.String(), http.StatusInternalServerError)
  203. return
  204. }
  205. w.Write([]byte("1"))
  206. addUpdate(pid, direction, &Song{Yid: q.Get("yid")})
  207. }
  208. func poll(w http.ResponseWriter, r *http.Request) {
  209. q := r.URL.Query()
  210. timestamp := q.Get("timestamp")
  211. if timestamp == "-1" {
  212. db := <-dbPool
  213. defer func () {dbPool <- db}()
  214. query, err := prepare(db,
  215. "SELECT `yid`,`title`,`user` FROM `playlist` JOIN `song` WHERE `id` = ? ORDER BY `order` ASC",
  216. q.Get("pid"))
  217. updates := make([]Update, 0, 2)
  218. for {
  219. song := new(Song)
  220. query.BindResult(&song.Yid, &song.Title, &song.User)
  221. eof, err := query.Fetch()
  222. if err != nil {
  223. http.Error(w, err.String(), http.StatusInternalServerError)
  224. return
  225. }
  226. if eof {
  227. break
  228. }
  229. updates = append(updates, Update{Song: song, Action: addAction, Timestamp: time.Nanoseconds()})
  230. }
  231. err = query.FreeResult()
  232. if err != nil {
  233. http.Error(w, err.String(), http.StatusInternalServerError)
  234. return
  235. }
  236. output, err := json.MarshalForHTML(updates)
  237. if err != nil {
  238. http.Error(w, err.String(), http.StatusInternalServerError)
  239. return
  240. }
  241. w.Write(output)
  242. } else {
  243. timestamp, err := strconv.Atoi64(q.Get("timestamp"))
  244. if err != nil {
  245. http.Error(w, err.String(), http.StatusInternalServerError)
  246. return
  247. }
  248. update := getUpdates(q.Get("pid"), timestamp)
  249. if update != nil {
  250. w.Write([]byte("["))
  251. for update != nil {
  252. output, err := json.MarshalForHTML(update)
  253. if err == nil {
  254. w.Write(output)
  255. }
  256. update = update.Next
  257. if update != nil {
  258. w.Write([]byte(","))
  259. }
  260. }
  261. w.Write([]byte("]"))
  262. return
  263. }
  264. w.Write([]byte("[]"))
  265. }
  266. }