server.lisp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. (in-package :cl-user)
  2. (defpackage chad-music.server
  3. (:use :cl #:alexandria #:chad-music.db #:jonathan)
  4. (:export #:main))
  5. (in-package :chad-music.server)
  6. (defvar *path-url-mappings* nil "alist map database paths to urls")
  7. (defun get-url (path)
  8. (declare #.*standard-optimize-settings*)
  9. (when (and path (pathnamep path))
  10. (let ((path (namestring path)))
  11. (loop for (path-prefix . url-prefix) in *path-url-mappings*
  12. do (multiple-value-bind (foundp suffix) (starts-with-subseq path-prefix path :return-suffix t)
  13. (when foundp
  14. (return-from get-url (concatenate 'string url-prefix
  15. (format nil "~{~A~^/~}"
  16. (mapcar #'quri:url-encode
  17. (split-sequence:split-sequence #\/ suffix)))))))))))
  18. (defmacro maybe-key-value (key value)
  19. `(when ,value
  20. (write-key-value ,key ,value)))
  21. (defmethod %to-json ((album album))
  22. (with-object
  23. (maybe-key-value "id" (album-id album))
  24. (maybe-key-value "artist" (album-artist album))
  25. (maybe-key-value "year" (album-year album))
  26. (maybe-key-value "album" (album-album album))
  27. (maybe-key-value "original_date" (album-original-date album))
  28. (maybe-key-value "publisher" (album-publisher album))
  29. (maybe-key-value "country" (album-country album))
  30. (maybe-key-value "genre" (album-genre album))
  31. (maybe-key-value "type" (or (album-type album) "album"))
  32. (maybe-key-value "status" (album-status album))
  33. (maybe-key-value "mb_id" (album-mb-id album))
  34. (maybe-key-value "track_count" (album-track-count album))
  35. (maybe-key-value "total_duration" (album-total-duration album))
  36. (maybe-key-value "cover" (get-url (album-cover album)))))
  37. (defmethod %to-json ((track track))
  38. (with-object
  39. (maybe-key-value "artist" (track-artist track))
  40. (maybe-key-value "album" (album-album (track-album track)))
  41. (maybe-key-value "year" (album-year (track-album track)))
  42. (maybe-key-value "no" (clear-track-no (track-no track)))
  43. (maybe-key-value "title" (track-title track))
  44. (maybe-key-value "bit_rate" (track-bit-rate track))
  45. (maybe-key-value "vbr" (if (track-is-vbr track) :true :false))
  46. (maybe-key-value "duration" (track-duration track))
  47. (maybe-key-value "url" (get-url (track-path track)))))
  48. (defparameter +400+ '(400 nil #.(trivial-utf-8:string-to-utf-8-bytes "400")))
  49. (defparameter +404+ '(404 nil #.(trivial-utf-8:string-to-utf-8-bytes "404")))
  50. (defparameter +200-empty+ '(200 (:content-type "application/json")
  51. #.(trivial-utf-8:string-to-utf-8-bytes "{}")))
  52. (defun 200-json (data &optional (dumper #'to-json))
  53. (declare #.*standard-optimize-settings*
  54. (type function dumper))
  55. `(200 (:content-type "application/json")
  56. ,(trivial-utf-8:string-to-utf-8-bytes (funcall dumper data))))
  57. (defun get-category-list (params)
  58. (declare #.*standard-optimize-settings*
  59. (ignore params))
  60. (200-json '("artist" "year" "album" "publisher" "country" "genre" "type" "status")))
  61. (let ((db-package (find-package :chad-music.db)))
  62. (defun getsym (place indicator)
  63. (declare #.*standard-optimize-settings*
  64. (type list place)
  65. (type symbol indicator))
  66. (intern (string-upcase (getf place indicator)) db-package)))
  67. (defun aget (place indicator &key (test #'equal))
  68. (declare #.*standard-optimize-settings*
  69. (type list place)
  70. (type (or string symbol) indicator)
  71. (type function test))
  72. (cdr (assoc indicator place :test test)))
  73. (defun may-integer (string)
  74. (declare #.*standard-optimize-settings*
  75. (type (or null string) string))
  76. (when string
  77. (parse-integer string :junk-allowed t)))
  78. (defun get-restrictions (query-params)
  79. (declare #.*standard-optimize-settings*
  80. (type list query-params))
  81. (loop for key in '(artist year album publisher country genre type status)
  82. for value = (aget query-params (string-downcase (symbol-name key)))
  83. when value collect (cons key (case key
  84. (year (parse-integer value :junk-allowed t))
  85. (otherwise value)))))
  86. (defparameter +max-limit+ 100)
  87. (defmacro with-category ((params category filter restrictions offset limit latest) &body body)
  88. (with-gensyms (query-string query-params)
  89. `(let ((,category (getsym ,params :category)))
  90. (case ,category
  91. ((artist year album publisher country genre type status)
  92. (let* ((,query-string (getf myway:*env* :query-string))
  93. (,query-params (and ,query-string (quri:url-decode-params ,query-string)))
  94. (,filter (aget ,query-params "filter"))
  95. (,restrictions (get-restrictions ,query-params))
  96. (,offset (or (may-integer (aget ,query-params "offset")) 0))
  97. (,limit (min (the fixnum +max-limit+)
  98. (the fixnum (or (may-integer (aget ,query-params "limit")) +max-limit+))))
  99. (,latest (aget ,query-params "latest")))
  100. ,@body))
  101. (otherwise +404+)))))
  102. (defun get-category-size (params)
  103. (declare #.*standard-optimize-settings*)
  104. (with-category (params category filter restrictions offset limit latest)
  105. (declare (ignore offset limit latest))
  106. (200-json (query-category category
  107. :filter filter :restrictions restrictions
  108. :count-only t))))
  109. (defun dumps-category-result (results)
  110. (with-output-to-string*
  111. (with-array
  112. (loop for (cat . count) in results
  113. do (write-item
  114. (with-object
  115. (write-key-value "item" cat)
  116. (write-key-value "count" count)))))))
  117. (defun get-category (params)
  118. (declare #.*standard-optimize-settings*)
  119. (with-category (params category filter restrictions offset limit latest)
  120. (200-json (query-category category
  121. :filter filter :restrictions restrictions
  122. :limit limit :offset offset :latest latest)
  123. (case category
  124. (album #'to-json)
  125. (t #'dumps-category-result)))))
  126. (defun get-album-tracks (params)
  127. (declare #.*standard-optimize-settings*)
  128. (200-json (album-tracks (getf params :id))))
  129. (defun file-server (root)
  130. (lambda (params)
  131. (declare #.*standard-optimize-settings*)
  132. (let ((file (probe-file (cl-fad:merge-pathnames-as-file
  133. root (car (getf params :splat))))))
  134. (if file (list 200 nil file) +404+))))
  135. (defvar *mapper* (myway:make-mapper))
  136. (myway:connect *mapper* "/api/cat/:category/size" 'get-category-size)
  137. (myway:connect *mapper* "/api/cat/:category" 'get-category)
  138. (myway:connect *mapper* "/api/cat" 'get-category-list)
  139. (myway:connect *mapper* "/api/album/:id/tracks" 'get-album-tracks)
  140. (defun main (&rest args &key (port 5000) (debug nil) (use-thread t) (serve-files nil) &allow-other-keys)
  141. ;; Load config file
  142. (when-let (file (probe-file
  143. (merge-pathnames "config.lisp"
  144. (asdf:component-pathname
  145. (asdf:find-system '#:chad-music)))))
  146. (load file))
  147. ;; Load database
  148. (when-let (file (probe-file *db-path*))
  149. (let ((*package* (find-package :chad-music.db)))
  150. (setf *db* (load-db file))))
  151. ;; Set up debug file server
  152. (when serve-files
  153. (loop for (path-prefix . url-prefix) in *path-url-mappings*
  154. do (myway:connect *mapper* (concatenate 'string url-prefix "*")
  155. (file-server path-prefix))))
  156. ;; Start application
  157. (apply #'clack:clackup
  158. (myway:to-app *mapper*)
  159. :server :woo
  160. :port port
  161. :debug debug
  162. :use-default-middlewares nil
  163. :use-thread use-thread
  164. (alexandria:remove-from-plist args :data :port :debug :use-thread)))