photo-store.lisp 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. (in-package #:photo-store)
  2. (eval-when (:compile-toplevel :load-toplevel :execute)
  3. (restas::register-pkgmodule-traits 'photo-store)
  4. (restas:reconnect-all-routes))
  5. ;; photos: id | album_id | filename | size | taken | width | height | lat | lon
  6. ;; albums: id | parent_id | path | date | name | description | cover_id | accessibility
  7. (defun imagep (path)
  8. (equal (string-downcase (pathname-type path)) "jpg"))
  9. (defun dirname (directory)
  10. (car (last (pathname-directory directory))))
  11. (defun guess-album-title (directory photos)
  12. (declare (ignore photos))
  13. (dirname directory))
  14. (defvar *album-path-format*
  15. '(:year "/" :year "-" (:month 2) "-" (:day 2) "-" :name "/"))
  16. (defun guess-album-path (directory photos)
  17. (let ((from (reduce #'local-time:timestamp-minimum photos :key (agetter :created-at)))
  18. (format (sublis `((:name . ,(dirname directory)))
  19. *album-path-format*)))
  20. (local-time:format-timestring nil from :format format)))
  21. (defun middle (seq)
  22. (elt seq (/ (length seq) 2)))
  23. (defun load-album (directory)
  24. (let ((photos (mapcar 'load-photo-info (remove-if-not 'imagep (uiop:directory-files directory)))))
  25. (when photos
  26. (list (cons :title (guess-album-title directory photos))
  27. (cons :path (guess-album-path directory photos))
  28. (cons :cover (middle photos))
  29. (cons :photos photos)))))
  30. (defun load-albums-from-dir (directory)
  31. (remove-if #'null (list* (load-album directory)
  32. (loop for subdir in (uiop:subdirectories directory)
  33. append (load-albums-from-dir subdir)))))
  34. (defvar *path-url-mapping* nil "alist of path-2-url mapping")
  35. (defun map-path (path)
  36. (loop for (base-path . base-url) in *path-url-mapping*
  37. for rel-path = (uiop:subpathp path base-path)
  38. when rel-path do (return (concatenate 'string base-url (namestring rel-path)))
  39. finally (error "Can't map path ~S" path)))
  40. (defun photo-item (info)
  41. `((:src . ,(map-path (aget :path info)))
  42. (:title . ,(aget :name info))
  43. (:w . ,(first (aget :dim info)))
  44. (:h . ,(second (aget :dim info)))))
  45. (defun album-item (album)
  46. `((:title . ,(aget :title album))
  47. (:cover . ,(photo-item (aget :cover album)))
  48. (:path . ,(namestring (aget :path album)))
  49. (:photos . ,(mapcar 'photo-item (aget :photos album)))))
  50. (restas:define-route main ("")
  51. (asdf:system-relative-pathname :photo-store "index.html"))
  52. ;; Assets file path
  53. (defparameter *assets-path*
  54. (asdf:system-relative-pathname :photo-store "assets/"))
  55. (restas:define-route assets/css ("css/:file" :content-type "text/css")
  56. (merge-pathnames file *assets-path*))
  57. (restas:define-route assets/js ("js/:file" :content-type "application/x-javascript")
  58. (merge-pathnames file *assets-path*))
  59. (defun json (object)
  60. (json:encode-json-to-string object))
  61. (defvar *incoming-path* nil "Path for incoming photos")
  62. (restas:define-route incoming ("incoming/"
  63. :content-type "application/json")
  64. (json (mapcar 'album-item (load-albums-from-dir *incoming-path*))))
  65. (defun start (&key (address "0.0.0.0") (port 8800))
  66. (restas:start '#:photo-store :address address :port port))