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:
parent
4efe9ac5a9
commit
618ce12fd8
17 changed files with 33 additions and 726 deletions
|
@ -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)))))
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")))))
|
||||
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
104
backend/vendor/storages/backend/local.clj
vendored
104
backend/vendor/storages/backend/local.clj
vendored
|
@ -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)))
|
||||
|
111
backend/vendor/storages/backend/misc.clj
vendored
111
backend/vendor/storages/backend/misc.clj
vendored
|
@ -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))
|
||||
|
52
backend/vendor/storages/core.clj
vendored
52
backend/vendor/storages/core.clj
vendored
|
@ -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))
|
168
backend/vendor/storages/fs.clj
vendored
168
backend/vendor/storages/fs.clj
vendored
|
@ -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)))
|
115
backend/vendor/storages/impl.clj
vendored
115
backend/vendor/storages/impl.clj
vendored
|
@ -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"))))
|
||||
|
38
backend/vendor/storages/proto.clj
vendored
38
backend/vendor/storages/proto.clj
vendored
|
@ -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."))
|
||||
|
Loading…
Add table
Reference in a new issue