main.go 8.1 KB

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