Low-level file-related functions for implementing Metabase plugin functionality. These use the As much as possible, this namespace aims to abstract away the | (ns metabase.util.files
(:require
[babashka.fs :as fs]
[clojure.java.io :as io]
[clojure.string :as str]
[metabase.util :as u]
[metabase.util.i18n :refer [trs]]
[metabase.util.log :as log])
(:import
(java.io FileNotFoundException)
(java.net URL)
(java.nio.file CopyOption Files FileSystem FileSystemAlreadyExistsException FileSystems
LinkOption OpenOption Path Paths StandardCopyOption)
(java.nio.file.attribute FileAttribute)
(java.util Collections)
(java.util.zip ZipInputStream))) |
(set! *warn-on-reflection* true) | |
--------------------------------------------------- Path Utils --------------------------------------------------- | |
(defn- get-path-in-filesystem ^Path [^FileSystem filesystem ^String path-component & more-components] (.getPath filesystem path-component (u/varargs String more-components))) | |
Get a (get-path "/Users/cam/metabase/metabase/plugins") ;; -> #object[sun.nio.fs.UnixPath 0x4d378139 "/Users/cam/metabase/metabase/plugins"] | (defn get-path ^Path [& path-components] (apply get-path-in-filesystem (FileSystems/getDefault) path-components)) |
Appends string | (defn append-to-path
^Path [^Path path & components]
(loop [^Path path path, [^String component & more] components]
(let [path (.resolve path component)]
(if-not (seq more)
path
(recur path more))))) |
----------------------------------------------- Other Basic Utils ------------------------------------------------ | |
Does file at | (defn exists? [^Path path] (Files/exists path (u/varargs LinkOption))) |
True if | (defn regular-file? [^Path path] (Files/isRegularFile path (u/varargs LinkOption))) |
True if we can read the file at | (defn readable? [^Path path] (Files/isReadable path)) |
----------------------------------------------- Working with Dirs ------------------------------------------------ | |
Self-explanatory. Create a directory with | (defn create-dir-if-not-exists!
[^Path path]
(when-let [parent (fs/parent path)]
(create-dir-if-not-exists! parent))
(when-not (exists? path)
(Files/createDirectory path (u/varargs FileAttribute)))) |
Get a sequence of all files in | (defn files-seq [^Path path] (iterator-seq (.iterator (Files/list path)))) |
------------------------------------------------- Copying Stuff -------------------------------------------------- | |
(defn- last-modified-timestamp ^java.time.Instant [^Path path]
(when (exists? path)
(.toInstant (Files/getLastModifiedTime path (u/varargs LinkOption))))) | |
Copy a file from | (defn copy-file!
[^Path source ^Path dest]
(when (or (not (exists? dest))
(not= (last-modified-timestamp source) (last-modified-timestamp dest)))
(log/infof "Extract file %s -> %s" source dest)
(Files/copy source dest (u/varargs CopyOption [StandardCopyOption/REPLACE_EXISTING
StandardCopyOption/COPY_ATTRIBUTES])))) |
Copy all files in | (defn copy-files!
[^Path source-dir, ^Path dest-dir]
(doseq [^Path source (files-seq source-dir)
:let [target (append-to-path dest-dir (str (.getFileName source)))]]
(try
(copy-file! source target)
(catch Throwable e
(log/error e "Failed to copy file"))))) |
------------------------------------------ Opening filesystems for URLs ------------------------------------------ | |
(defn- url-inside-jar? [^URL url]
(when url
(str/includes? (.getFile url) ".jar!/"))) | |
(defn- jar-file-system-from-url ^FileSystem [^URL url]
(let [uri (.toURI url)]
(try
(FileSystems/newFileSystem uri Collections/EMPTY_MAP)
(catch FileSystemAlreadyExistsException _
(log/info "File system at" uri "already exists")
(FileSystems/getFileSystem uri))))) | |
Impl for | (defn do-with-open-path-to-resource
[resource f]
{:pre [(some? resource)]}
(let [url (io/resource resource)]
(when-not url
(throw (FileNotFoundException. (trs "Resource does not exist."))))
(if (url-inside-jar? url)
(with-open [fs (jar-file-system-from-url url)]
(f (get-path-in-filesystem fs "/" resource)))
(f (get-path (.toString (Paths/get (.toURI url)))))))) |
Execute Throws a FileNotFoundException if the resource does not exist; be sure to check with (with-open-path-to-resouce [path "modules"] ...) | (defmacro with-open-path-to-resource
[[path-binding resource-filename-str] & body]
`(do-with-open-path-to-resource
~resource-filename-str
(fn [~(vary-meta path-binding assoc :tag java.nio.file.Path)]
~@body))) |
+----------------------------------------------------------------------------------------------------------------+ | JAR FILE CONTENTS | +----------------------------------------------------------------------------------------------------------------+ | |
True is a file exists in an archive. | (defn file-exists-in-archive?
[^Path archive-path & path-components]
(with-open [fs (FileSystems/newFileSystem archive-path (ClassLoader/getSystemClassLoader))]
(let [file-path (apply get-path-in-filesystem fs path-components)]
(exists? file-path)))) |
Read the entire contents of a file from a archive (such as a JAR). | (defn slurp-file-from-archive
[^Path archive-path & path-components]
(with-open [fs (FileSystems/newFileSystem archive-path (ClassLoader/getSystemClassLoader))]
(let [file-path (apply get-path-in-filesystem fs path-components)]
(when (exists? file-path)
(with-open [is (Files/newInputStream file-path (u/varargs OpenOption))]
(slurp is)))))) |
Decompress a zip archive from input to output. | (defn unzip-file
[zip-file mod-fn]
(with-open [stream (-> zip-file io/input-stream ZipInputStream.)]
(loop [entry (.getNextEntry stream)]
(when entry
(let [out-path (mod-fn (.getName entry))
out-file (io/file out-path)]
(if (.isDirectory entry)
(when-not (.exists out-file) (.mkdirs out-file))
(let [parent-dir (fs/parent out-path)]
(when-not (fs/exists? (str parent-dir)) (fs/create-dirs parent-dir))
(io/copy stream out-file)))
(recur (.getNextEntry stream))))))) |
Returns a java.nio.file.Path | (defn relative-path [path] (fs/relativize (fs/absolutize ".") path)) |