0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-08 08:09:14 -05:00

Replace internal storage impl with datoteka library.

This commit is contained in:
Andrey Antukh 2017-02-25 16:00:22 +01:00
parent 4efe9ac5a9
commit 618ce12fd8
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
17 changed files with 33 additions and 726 deletions

View file

@ -12,8 +12,8 @@
[mount.core :as mount]
[cuerdas.core :as str]
[suricatta.core :as sc]
[storages.core :as st]
[storages.fs :as fs]
[datoteka.storages :as st]
[datoteka.core :as fs]
[uxbox.config]
[uxbox.db :as db]
[uxbox.migrations]
@ -68,7 +68,7 @@
{:pre [(fs/path? localpath)
(uuid? collid)
(uuid? iconid)]}
(let [filename (fs/base-name localpath)
(let [filename (fs/name localpath)
extension (second (fs/split-ext filename))
data (svg/parse localpath)
params {:name (:name data filename)
@ -85,7 +85,7 @@
(defn- import-icon
[conn id fpath]
{:pre [(uuid? id) (fs/path? fpath)]}
(let [filename (fs/base-name fpath)
(let [filename (fs/name fpath)
iconid (uuid/namespaced +icons-uuid-ns+ (str id filename))]
(when-not (retrieve-icon conn iconid)
(println "Importing icon:" (str fpath))
@ -95,8 +95,9 @@
[conn basedir {:keys [path regex] :as entry}]
{:pre [(us/valid? ::import-entry entry)]}
(let [id (create-icons-collection conn entry)
path (fs/resolve basedir path)]
(doseq [fpath (fs/list-files path)]
path (fs/join basedir path)]
(doseq [fpath (->> (fs/list-dir path)
(filter fs/regular-file?))]
(when (re-matches regex (str fpath))
(import-icon conn id fpath)))))
@ -137,7 +138,7 @@
{:pre [(fs/path? localpath)
(uuid? collid)
(uuid? imageid)]}
(let [filename (fs/base-name localpath)
(let [filename (fs/name localpath)
storage media/images-storage
[width height] (retrieve-image-size localpath)
extension (second (fs/split-ext filename))
@ -157,7 +158,7 @@
(defn- import-image
[conn id fpath]
{:pre [(uuid? id) (fs/path? fpath)]}
(let [filename (fs/base-name fpath)
(let [filename (fs/name fpath)
imageid (uuid/namespaced +images-uuid-ns+ (str id filename))]
(when-not (retrieve-image conn imageid)
(println "Importing image:" (str fpath))
@ -167,8 +168,9 @@
[conn basedir {:keys [path regex] :as entry}]
{:pre [(us/valid? ::import-entry entry)]}
(let [id (create-images-collection conn entry)
path (fs/resolve basedir path)]
(doseq [fpath (fs/list-files path)]
path (fs/join basedir path)]
(doseq [fpath (->> (fs/list-dir path)
(filter fs/regular-file?))]
(when (re-matches regex (str fpath))
(import-image conn id fpath)))))

View file

@ -8,7 +8,6 @@
(:require [clojure.spec :as s]
[promesa.core :as p]
[catacumba.http :as http]
[storages.core :as st]
[uxbox.util.spec :as us]
[uxbox.services :as sv]
[uxbox.util.response :refer (rsp)]

View file

@ -8,8 +8,8 @@
(:require [clojure.spec :as s]
[promesa.core :as p]
[catacumba.http :as http]
[storages.core :as st]
[storages.fs :as fs]
[datoteka.storages :as st]
[datoteka.core :as fs]
[uxbox.media :as media]
[uxbox.images :as images]
[uxbox.util.spec :as us]
@ -116,7 +116,7 @@
(let [{:keys [file id width height
mimetype collection]} (us/conform ::create-image data)
id (or id (uuid/random))
filename (fs/base-name file)
filename (fs/name file)
storage media/images-storage]
(letfn [(persist-image-entry [path]
(sv/novelty {:id id

View file

@ -8,8 +8,8 @@
(:require [clojure.spec :as s]
[promesa.core :as p]
[catacumba.http :as http]
[storages.core :as st]
[storages.fs :as fs]
[datoteka.storages :as st]
[datoteka.core :as fs]
[uxbox.media :as media]
[uxbox.images :as images]
[uxbox.util.spec :as us]
@ -80,7 +80,7 @@
(defn update-photo
[{user :identity data :data}]
(letfn [(store-photo [file]
(let [filename (fs/base-name file)
(let [filename (fs/name file)
storage media/images-storage]
(st/save storage filename file)))
(assign-photo [path]

View file

@ -7,8 +7,10 @@
(ns uxbox.images
"Image postprocessing."
(:require [clojure.spec :as s]
[storages.core :as st]
[storages.fs :as fs]
[clojure.java.io :as io]
[datoteka.storages :as st]
[datoteka.core :as fs]
[datoteka.proto :as pt]
[uxbox.util.spec :as us]
[uxbox.media :as media]
[uxbox.util.images :as images]

View file

@ -6,15 +6,13 @@
(ns uxbox.media
"A media storage impl for uxbox."
(:require [mount.core :as mount :refer (defstate)]
(:require [mount.core :refer [defstate]]
[clojure.java.io :as io]
[cuerdas.core :as str]
[storages.core :as st]
[storages.backend.local :refer (localfs)]
[storages.backend.misc :refer (hashed scoped)]
[uxbox.config :refer (config)]))
;; FIXME: migrate from storages to datoteka
[datoteka.storages :as st]
[datoteka.storages.local :refer [localfs]]
[datoteka.storages.misc :refer [hashed scoped]]
[uxbox.config :refer [config]]))
;; --- State

View file

@ -9,7 +9,7 @@
(:refer-clojure :exclude [with-open])
(:require [clojure.java.io :as io]
[suricatta.core :as sc]
[storages.fs :as fs]
[datoteka.core :as fs]
[uxbox.db :as db]
[uxbox.sql :as sql]
[uxbox.util.uuid :as uuid]

View file

@ -9,8 +9,8 @@
(:require [clojure.spec :as s]
[promesa.core :as p]
[suricatta.core :as sc]
[storages.core :as st]
[storages.fs :as fs]
[datoteka.storages :as st]
[datoteka.core :as fs]
[uxbox.config :as ucfg]
[uxbox.util.spec :as us]
[uxbox.sql :as sql]
@ -184,7 +184,7 @@
(ex/raise :type :validation
:code ::image-does-not-exists))
(let [path @(st/lookup storage (:path image))
filename (fs/base-name path)
filename (fs/name path)
path @(st/save storage filename path)
image (assoc image
:path (str path)

View file

@ -1,106 +0,0 @@
(ns storages.tests
(:require [clojure.test :as t]
[storages.core :as st]
[storages.backend.local :as local]
[storages.backend.misc :as misc])
(:import java.io.File
org.apache.commons.io.FileUtils))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Test Fixtures
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- clean-temp-directory
[next]
(next)
(let [directory (File. "/tmp/catacumba/")]
(FileUtils/deleteDirectory directory)))
(t/use-fixtures :each clean-temp-directory)
;; --- Tests: FileSystemStorage
(t/deftest test-localfs-store-and-lookup
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
rpath @(st/save storage "test.txt" "my content")
fpath @(st/lookup storage rpath)
fdata (slurp fpath)]
(t/is (= (str fpath) "/tmp/catacumba/test/test.txt"))
(t/is (= "my content" fdata))))
(t/deftest test-localfs-store-and-get-public-url
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
rpath @(st/save storage "test.txt" "my content")
ruri (st/public-url storage rpath)]
(t/is (= (str ruri) "http://localhost:5050/test.txt"))))
(t/deftest test-localfs-store-and-lookup-with-subdirs
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
rpath @(st/save storage "somepath/test.txt" "my content")
fpath @(st/lookup storage rpath)
fdata (slurp fpath)]
(t/is (= (str fpath) "/tmp/catacumba/test/somepath/test.txt"))
(t/is (= "my content" fdata))))
(t/deftest test-localfs-store-and-delete-and-check
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
rpath @(st/save storage "test.txt" "my content")]
(t/is @(st/delete storage rpath))
(t/is (not @(st/exists? storage rpath)))))
(t/deftest test-localfs-store-duplicate-file-raises-exception
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})]
(t/is @(st/save storage "test.txt" "my content"))
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/save storage "test.txt" "my content")))))
(t/deftest test-localfs-access-unauthorized-path
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})]
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/lookup storage "../test.txt")))
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/lookup storage "/test.txt")))))
;; --- Tests: ScopedPathStorage
(t/deftest test-localfs-scoped-store-and-lookup
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
storage (misc/scoped storage "some/prefix")
rpath @(st/save storage "test.txt" "my content")
fpath @(st/lookup storage rpath)
fdata (slurp fpath)]
(t/is (= (str fpath) "/tmp/catacumba/test/some/prefix/test.txt"))
(t/is (= "my content" fdata))))
(t/deftest test-localfs-scoped-store-and-delete-and-check
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
storage (misc/scoped storage "some/prefix")
rpath @(st/save storage "test.txt" "my content")]
(t/is @(st/delete storage rpath))
(t/is (not @(st/exists? storage rpath)))))
(t/deftest test-localfs-scoped-store-duplicate-file-raises-exception
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
storage (misc/scoped storage "some/prefix")]
(t/is @(st/save storage "test.txt" "my content"))
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/save storage "test.txt" "my content")))))
(t/deftest test-localfs-scoped-access-unauthorized-path
(let [storage (local/localfs {:basedir "/tmp/catacumba/test"
:baseuri "http://localhost:5050/"})
storage (misc/scoped storage "some/prefix")]
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/lookup storage "../test.txt")))
(t/is (thrown? java.util.concurrent.ExecutionException
@(st/lookup storage "/test.txt")))))

View file

@ -5,7 +5,7 @@
[buddy.core.codecs :as codecs]
[catacumba.serializers :as sz]
[mount.core :as mount]
[storages.core :as st]
[datoteka.storages :as st]
[suricatta.core :as sc]
[uxbox.services.auth :as usa]
[uxbox.services.users :as usu]

View file

@ -4,7 +4,7 @@
[suricatta.core :as sc]
[clojure.java.io :as io]
[catacumba.testing :refer (with-server)]
[storages.core :as st]
[datoteka.storages :as st]
[uxbox.db :as db]
[uxbox.sql :as sql]
[uxbox.media :as media]

View file

@ -1,104 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.backend.local
"A local filesystem storage implementation."
(:require [promesa.core :as p]
[clojure.java.io :as io]
[executors.core :as exec]
[storages.proto :as pt]
[storages.impl :as impl]
[storages.fs :as fs])
(:import java.io.InputStream
java.io.OutputStream
java.net.URI
java.nio.file.Path
java.nio.file.Files))
(defn normalize-path
[^Path base ^Path path]
(if (fs/absolute? path)
(throw (ex-info "Suspicios operation: absolute path not allowed."
{:path (str path)}))
(let [^Path fullpath (.resolve base path)
^Path fullpath (.normalize fullpath)]
(when-not (.startsWith fullpath base)
(throw (ex-info "Suspicios operation: go to parent dir is not allowed."
{:path (str path)})))
fullpath)))
(defn- save
[base path content]
(let [^Path path (pt/-path path)
^Path fullpath (normalize-path base path)]
(when-not (fs/exists? (.getParent fullpath))
(fs/create-dir! (.getParent fullpath)))
(with-open [^InputStream source (pt/-input-stream content)
^OutputStream dest (Files/newOutputStream
fullpath fs/write-open-opts)]
(io/copy source dest)
path)))
(defn- delete
[base path]
(let [path (->> (pt/-path path)
(normalize-path base))]
(Files/deleteIfExists ^Path path)))
(defrecord LocalFileSystemBackend [^Path base ^URI baseuri]
pt/IPublicStorage
(-public-uri [_ path]
(.resolve baseuri (str path)))
pt/IStorage
(-save [_ path content]
(exec/submit (partial save base path content)))
(-delete [_ path]
(exec/submit (partial delete base path)))
(-exists? [this path]
(try
(p/resolved
(let [path (->> (pt/-path path)
(normalize-path base))]
(fs/exists? path)))
(catch Exception e
(p/rejected e))))
pt/IClearableStorage
(-clear [_]
(fs/delete-dir! base)
(fs/create-dir! base))
pt/ILocalStorage
(-lookup [_ path']
(try
(p/resolved
(->> (pt/-path path')
(normalize-path base)))
(catch Exception e
(p/rejected e)))))
(defn localfs
"Create an instance of local FileSystem storage providing an
absolute base path.
If that path does not exists it will be automatically created,
if it exists but is not a directory, an exception will be
raised."
[{:keys [basedir baseuri] :as keys}]
(let [^Path basepath (pt/-path basedir)
^URI baseuri (pt/-uri baseuri)]
(when (and (fs/exists? basepath)
(not (fs/directory? basepath)))
(throw (ex-info "File already exists." {})))
(when-not (fs/exists? basepath)
(fs/create-dir! basepath))
(->LocalFileSystemBackend basepath baseuri)))

View file

@ -1,111 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.backend.misc
"A local filesystem storage implementation."
(:require [promesa.core :as p]
[cuerdas.core :as str]
[buddy.core.codecs :as codecs]
[buddy.core.codecs.base64 :as b64]
[buddy.core.nonce :as nonce]
[buddy.core.hash :as hash]
[storages.proto :as pt]
[storages.impl :as impl]
[storages.backend.local :as local])
(:import java.io.InputStream
java.io.OutputStream
java.nio.file.Path
java.nio.file.Files))
;; --- Scoped Storage
(defrecord ScopedBackend [storage ^Path prefix]
pt/IPublicStorage
(-public-uri [_ path]
(let [^Path path (pt/-path [prefix path])]
(pt/-public-uri storage path)))
pt/IStorage
(-save [_ path content]
(let [^Path path (pt/-path [prefix path])]
(->> (pt/-save storage path content)
(p/map (fn [^Path path]
(.relativize prefix path))))))
(-delete [_ path]
(let [^Path path (pt/-path [prefix path])]
(pt/-delete storage path)))
(-exists? [this path]
(let [^Path path (pt/-path [prefix path])]
(pt/-exists? storage path)))
pt/ILocalStorage
(-lookup [_ path]
(->> (pt/-lookup storage "")
(p/map (fn [^Path base]
(let [base (pt/-path [base prefix])]
(->> (pt/-path path)
(local/normalize-path base))))))))
(defn scoped
"Create a composed storage instance that automatically prefixes
the path when content is saved. For the rest of methods it just
relies to the underlying storage.
This is usefull for atomatically add sertain prefix to some
uploads."
[storage prefix]
(let [prefix (pt/-path prefix)]
(->ScopedBackend storage prefix)))
;; --- Hashed Storage
(defn- generate-path
[^Path path]
(let [name (str (.getFileName path))
hash (-> (nonce/random-nonce 128)
(hash/blake2b-256)
(b64/encode true)
(codecs/bytes->str))
tokens (re-seq #"[\w\d\-\_]{3}" hash)
path-tokens (take 6 tokens)
rest-tokens (drop 6 tokens)
path (pt/-path path-tokens)
frest (apply str rest-tokens)]
(pt/-path (list path frest name))))
(defrecord HashedBackend [storage]
pt/IPublicStorage
(-public-uri [_ path]
(pt/-public-uri storage path))
pt/IStorage
(-save [_ path content]
(let [^Path path (pt/-path path)
^Path path (generate-path path)]
(pt/-save storage path content)))
(-delete [_ path]
(pt/-delete storage path))
(-exists? [this path]
(pt/-exists? storage path))
pt/ILocalStorage
(-lookup [_ path]
(pt/-lookup storage path)))
(defn hashed
"Create a composed storage instance that uses random
hash based directory tree distribution for the final
file path.
This is usefull when you want to store files with
not predictable uris."
[storage]
(->HashedBackend storage))

View file

@ -1,52 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.core
"A storages abstraction layer."
(:require [storages.proto :as pt]
[storages.impl]))
(defn save
"Perists a file or bytes in the storage. This function
returns a relative path where file is saved.
The final file path can be different to the one provided
to this function and the behavior is totally dependen on
the storage implementation."
[storage path content]
(pt/-save storage path content))
(defn lookup
"Resolve provided relative path in the storage and return
the local filesystem absolute path to it.
This method may be not implemented in all storages."
[storage path]
{:pre [(satisfies? pt/ILocalStorage storage)]}
(pt/-lookup storage path))
(defn exists?
"Check if a relative `path` exists in the storage."
[storage path]
(pt/-exists? storage path))
(defn delete
"Delete a file from the storage."
[storage path]
(pt/-delete storage path))
(defn clear!
"Clear all contents of the storage."
[storage]
(pt/-clear storage))
(defn public-url
[storage path]
(pt/-public-uri storage path))
(defn storage?
"Return `true` if `v` implements IStorage protocol"
[v]
(satisfies? pt/IStorage v))

View file

@ -1,168 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.fs
"File System helpers."
(:refer-clojure :exclude [name resolve])
(:require [storages.proto :as pt])
(:import java.nio.file.Path
java.nio.file.Files
java.nio.file.LinkOption
java.nio.file.OpenOption
java.nio.file.CopyOption
java.nio.file.StandardOpenOption
java.nio.file.StandardCopyOption
java.nio.file.SimpleFileVisitor
java.nio.file.FileVisitResult
java.nio.file.attribute.FileAttribute
java.nio.file.attribute.PosixFilePermissions
ratpack.form.UploadedFile))
;; --- Constants
(def write-open-opts
(->> [StandardOpenOption/TRUNCATE_EXISTING
StandardOpenOption/CREATE
StandardOpenOption/WRITE]
(into-array OpenOption)))
(def read-open-opts
(->> [StandardOpenOption/READ]
(into-array OpenOption)))
(def move-opts
(->> [StandardCopyOption/ATOMIC_MOVE
StandardCopyOption/REPLACE_EXISTING]
(into-array CopyOption)))
(def follow-link-opts
(into-array LinkOption [LinkOption/NOFOLLOW_LINKS]))
;; --- Path Helpers
(defn path
"Create path from string or more than one string."
([fst]
(pt/-path fst))
([fst & more]
(pt/-path (cons fst more))))
(defn make-file-attrs
"Generate a array of `FileAttribute` instances
generated from `rwxr-xr-x` kind of expressions."
[^String expr]
(let [perms (PosixFilePermissions/fromString expr)
attr (PosixFilePermissions/asFileAttribute perms)]
(into-array FileAttribute [attr])))
(defn path?
"Return `true` if provided value is an instance of Path."
[v]
(instance? Path v))
(defn absolute?
"Return `true` if the provided path is absolute, `else` in case contrary.
The `path` parameter can be anything convertible to path instance."
[path]
(let [^Path path (pt/-path path)]
(.isAbsolute path)))
(defn exists?
"Return `true` if the provided path exists, `else` in case contrary.
The `path` parameter can be anything convertible to path instance."
[path]
(let [^Path path (pt/-path path)]
(Files/exists path follow-link-opts)))
(defn directory?
"Return `true` if the provided path is a directory, `else` in case contrary.
The `path` parameter can be anything convertible to path instance."
[path]
(let [^Path path (pt/-path path)]
(Files/isDirectory path follow-link-opts)))
(defn parent
"Get parent path if it exists."
[path]
(.getParent ^Path (pt/-path path)))
(defn base-name
"Get the file name."
[path]
(if (instance? UploadedFile path)
(.getFileName ^UploadedFile path)
(str (.getFileName ^Path (pt/-path path)))))
(defn split-ext
"Returns a vector of `[name extension]`."
[path]
(let [base (base-name path)
i (.lastIndexOf base ".")]
(if (pos? i)
[(subs base 0 i) (subs base i)]
[base nil])))
(defn extension
"Return the extension part of a file."
[path]
(last (split-ext path)))
(defn name
"Return the name part of a file."
[path]
(first (split-ext path)))
(defn resolve
"Resolve path on top of an other path."
[base path]
(let [^Path base (pt/-path base)
^Path path (pt/-path path)]
(-> (.resolve base path)
(.normalize))))
(defn list-directory
[path]
(let [path (pt/-path path)]
(with-open [stream (Files/newDirectoryStream path)]
(vec stream))))
(defn list-files
[path]
(filter (complement directory?) (list-directory path)))
;; --- Side-Effectfull Operations
(defn create-dir!
"Create a new directory."
[path]
(let [^Path path (pt/-path path)
attrs (make-file-attrs "rwxr-xr-x")]
(Files/createDirectories path attrs)))
(defn create-dir!
"Create a new directory."
[path]
(let [^Path path (pt/-path path)
attrs (make-file-attrs "rwxr-xr-x")]
(Files/createDirectories path attrs)))
(defn delete-dir!
[path]
(let [path (pt/-path path)
visitor (proxy [SimpleFileVisitor] []
(visitFile [file attrs]
(Files/delete file)
FileVisitResult/CONTINUE)
(postVisitDirectory [dir exc]
(Files/delete dir)
FileVisitResult/CONTINUE))]
(Files/walkFileTree path visitor)))
(defn create-tempfile
"Create a temporal file."
[& {:keys [suffix prefix]}]
(->> (make-file-attrs "rwxr-xr-x")
(Files/createTempFile prefix suffix)))

View file

@ -1,115 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.impl
"Implementation details and helpers."
(:require [storages.proto :as pt]
[storages.fs :as fs]
[buddy.core.codecs :as codecs]
[clojure.java.io :as io])
(:import java.io.File
java.io.ByteArrayInputStream
java.io.InputStream
java.net.URL
java.net.URI
java.nio.file.Path
java.nio.file.Paths
java.nio.file.Files))
(extend-protocol pt/IContent
String
(-input-stream [v]
(ByteArrayInputStream. (codecs/str->bytes v)))
Path
(-input-stream [v]
(io/input-stream v))
File
(-input-stream [v]
(io/input-stream v))
URI
(-input-stream [v]
(io/input-stream v))
URL
(-input-stream [v]
(io/input-stream v))
InputStream
(-input-stream [v]
v)
ratpack.http.TypedData
(-input-stream [this]
(.getInputStream this)))
(extend-protocol pt/IUri
URI
(-uri [v] v)
String
(-uri [v] (URI. v)))
(def ^:private empty-string-array
(make-array String 0))
(extend-protocol pt/IPath
Path
(-path [v] v)
URI
(-path [v] (Paths/get v))
URL
(-path [v] (Paths/get (.toURI v)))
String
(-path [v] (Paths/get v empty-string-array))
clojure.lang.Sequential
(-path [v]
(reduce #(.resolve %1 %2)
(pt/-path (first v))
(map pt/-path (rest v)))))
(defn- path->input-stream
[^Path path]
(Files/newInputStream path fs/read-open-opts))
(defn- path->output-stream
[^Path path]
(Files/newOutputStream path fs/write-open-opts))
(extend-type Path
io/IOFactory
(make-reader [path opts]
(let [^InputStream is (path->input-stream path)]
(io/make-reader is opts)))
(make-writer [path opts]
(let [^OutputStream os (path->output-stream path)]
(io/make-writer os opts)))
(make-input-stream [path opts]
(let [^InputStream is (path->input-stream path)]
(io/make-input-stream is opts)))
(make-output-stream [path opts]
(let [^OutputStream os (path->output-stream path)]
(io/make-output-stream os opts))))
(extend-type ratpack.http.TypedData
io/IOFactory
(make-reader [td opts]
(let [^InputStream is (.getInputStream td)]
(io/make-reader is opts)))
(make-writer [path opts]
(throw (UnsupportedOperationException. "read only object")))
(make-input-stream [td opts]
(let [^InputStream is (.getInputStream td)]
(io/make-input-stream is opts)))
(make-output-stream [path opts]
(throw (UnsupportedOperationException. "read only object"))))

View file

@ -1,38 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns storages.proto
"A storage abstraction definition.")
(defprotocol IUri
(-uri [_] "Coerce to uri."))
(defprotocol IPath
(-path [_] "Coerce to path."))
(defprotocol IContent
(-input-stream [_] "Coerce to input stream."))
(defprotocol IStorage
"A basic abstraction for storage access."
(-save [_ path content] "Persist the content under specified path.")
(-delete [_ path] "Delete the file by its path.")
(-exists? [_ path] "Check if file exists by path."))
(defprotocol IClearableStorage
(-clear [_] "clear all contents of the storage"))
(defprotocol IPublicStorage
(-public-uri [_ path] "Get a public accessible uri for path."))
(defprotocol ILocalStorage
(-lookup [_ path] "Resolves the path to the local filesystem."))
(defprotocol IStorageIntrospection
(-accessed-time [_ path] "Return the last accessed time of the file.")
(-created-time [_ path] "Return the creation time of the file.")
(-modified-time [_ path] "Return the last modified time of the file."))