git.fiddlerwoaroof.com
Raw Blame History
(fwoar.sp-user:define-package :stream-provider
  (:use :fw.lu)
  (:export #:get-stream-for
           #:stream-provider
           #:string-provider
           #:file-provider
           #:stream-key
           #:root
           #:streams
           #:with-storage-stream
           #:get-nested-provider
           #:absolute-path))
(cl:in-package :stream-provider)

(defclass stream-provider ()
  ())

(defvar *store-root* *default-pathname-defaults*)

(defgeneric stream-key (provider item)
  (:documentation "return a key for an item: must be able to be compared with EQUAL

If you override on provider, make sure to CALL-NEXT-METHOD"))

(defgeneric get-stream-for (provider streamable &rest extra-args)
  (:documentation "get a stream for a given streamable object"))

(defgeneric get-nested-provider (provider streamable)
  (:documentation "implement to handle storage of hierarchical data"))

(defgeneric root (provider)
  (:documentation "get the base path for the streams"))


(defclass string-provider (stream-provider)
  ((%streams :reader streams :initform (make-hash-table :test 'equal))))

(defmethod root ((provider string-provider))
  #p "/")

(defmethod stream-key :around (provider item)
  (declare (optimize (debug 3)))
  (let ((key (call-next-method)))
    (check-type key (or cons string pathname))
    (let ((the-pathname
           (ctypecase key
             (pathname key)
             (string (pathname key))
             (cons (uiop:merge-pathnames*
                    (car (last key))
                    (apply 'path-join
                           (mapcar 'uiop:ensure-directory-pathname
                                   (butlast key))))))))
      (uiop:enough-pathname the-pathname
                            (root provider)))))


(defmethod get-stream-for ((provider string-provider) streamable &rest extra-args)
  (declare (ignore extra-args))
  (with-accessors* (streams) provider
    (vector-update-stream:make-update-stream
     (setf (gethash (stream-key provider streamable) streams)
           (make-array 10
                       :element-type 'octet
                       :adjustable t
                       :fill-pointer 0)))))

(defclass file-provider (stream-provider)
  ((%root :initarg :root :initform (error "need a root for a file-provider") :reader root)
   (%if-exists :initarg :if-exists :initform :supersede :reader if-exists)))

(defgeneric absolute-path (provider path)
  (:method ((provider stream-provider) (path string))
    (absolute-path provider (parse-namestring path)))
  (:method ((provider stream-provider) (path pathname))
    (merge-pathnames path
                     (root provider))))

(defmethod get-stream-for ((provider file-provider) streamable &rest extra-args)
  (declare (ignore extra-args))
  (with-accessors* (if-exists root) provider
    (let ((stream-key (merge-pathnames (stream-key provider streamable)
                                       root)))
      (when (eql if-exists :if-exists)
        (ensure-directories-exist stream-key))
      (open stream-key :direction :output :if-exists if-exists
                       :element-type 'octet))))

(defmacro with-storage-stream ((stream-sym object provider &rest extra-args) &body body)
  (once-only (object)
    `(let ((,stream-sym (flexi-streams:make-flexi-stream (get-stream-for ,provider ,object ,@extra-args)
                                                         :external-format :utf-8)))
       (unwind-protect (progn ,@body)
         (close ,stream-sym)))))