photo-store.lisp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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 dirname (directory)
  8. (car (last (pathname-directory directory))))
  9. (defun guess-album-title (directory photos)
  10. (declare (ignore photos))
  11. (dirname directory))
  12. (defvar *album-path-format*
  13. '(:year "/" :year "-" (:month 2) "-" (:day 2) "-" :name "/"))
  14. (defun guess-album-path (directory photos)
  15. (let ((from (reduce #'local-time:timestamp-minimum photos :key (agetter :created-at)))
  16. (format (sublis `((:name . ,(dirname directory)))
  17. *album-path-format*)))
  18. (local-time:format-timestring nil from :format format)))
  19. (defun middle (seq)
  20. (elt seq (/ (length seq) 2)))
  21. (defun imagep (path)
  22. (equal (string-downcase (pathname-type path)) "jpg"))
  23. (defun directory-images (directory)
  24. (remove-if-not 'imagep (uiop:directory-files directory)))
  25. (defun load-album (directory)
  26. (let ((photos (mapcar 'load-photo-info (directory-images directory))))
  27. (when photos
  28. (list (cons :title (guess-album-title directory photos))
  29. (cons :path directory)
  30. (cons :cover (middle photos))
  31. (cons :photos photos)))))
  32. (defun load-albums-from-dir (directory)
  33. (remove-if #'null (list* (load-album directory)
  34. (loop for subdir in (uiop:subdirectories directory)
  35. append (load-albums-from-dir subdir)))))
  36. (defvar *path-url-mapping* nil "alist of path-2-url mapping")
  37. (defun map-path (path)
  38. (loop for (base-path . base-url) in *path-url-mapping*
  39. for rel-path = (uiop:subpathp path base-path)
  40. when rel-path do (return (concatenate 'string base-url (namestring rel-path)))
  41. finally (error "Can't map path ~S" path)))
  42. (defun photo-item (info &optional incoming)
  43. `((:src . ,(map-path (aget :path info)))
  44. ,@(when incoming (list (cons :path (namestring (aget :path info)))))
  45. (:w . ,(first (aget :dim info)))
  46. (:h . ,(second (aget :dim info)))))
  47. (defun album-item (album &optional incoming)
  48. `((:title . ,(aget :title album))
  49. (:cover . ,(photo-item (aget :cover album)))
  50. ,@(when incoming (list (cons :path (namestring (aget :path album)))
  51. (cons :dest (guess-album-path (aget :path album)
  52. (aget :photos album)))))
  53. (:photos . ,(mapcar #'(lambda (p) (photo-item p incoming)) (aget :photos album)))))
  54. (defun other-images (directory images)
  55. (remove-if #'(lambda (img) (member img images :test #'equal))
  56. (directory-images directory)))
  57. (defun directories (files)
  58. (delete-duplicates (mapcar #'uiop:pathname-directory-pathname files) :test #'equal))
  59. (defun move-file-to (file dest)
  60. (uiop:rename-file-overwriting-target
  61. file (uiop:make-pathname* :defaults file :directory (pathname-directory dest))))
  62. (defun move-images-with-folders (files dest)
  63. (labels ((move (files dest)
  64. (dolist (f files)
  65. (move-file-to f dest))))
  66. (let ((dirs (directories files)))
  67. (ensure-directories-exist dest)
  68. (move files dest)
  69. ;; Move all non-image files from original folders if no images left there
  70. (dolist (dir dirs)
  71. (unless (other-images dir files)
  72. (move (uiop:directory-files dir) dest)
  73. (uiop:delete-empty-directory dir))))))
  74. (restas:define-route main ("")
  75. (asdf:system-relative-pathname :photo-store "index.html"))
  76. ;; Assets file path
  77. (defparameter *assets-path*
  78. (asdf:system-relative-pathname :photo-store "assets/"))
  79. (restas:define-route assets/css ("css/:file" :content-type "text/css")
  80. (merge-pathnames file *assets-path*))
  81. (restas:define-route assets/js ("js/:file" :content-type "application/x-javascript")
  82. (merge-pathnames file *assets-path*))
  83. (defun json (object)
  84. (json:encode-json-to-string object))
  85. (defvar *incoming-path* nil "Path for incoming photos")
  86. (restas:define-route incoming ("incoming/"
  87. :content-type "application/json")
  88. (json (mapcar 'album-item (load-albums-from-dir *incoming-path*))))
  89. (defvar *photo-storage-path* nil "Destination storage path")
  90. (defun start (&key (address "0.0.0.0") (port 8800))
  91. (restas:start '#:photo-store :address address :port port))