main.go 7.8 KB

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