Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic Clojure to copy resources from running jar to outside

It seems like a classical problem but I can't find anything about it "the clojure way".

So, I have a foo/ directory inside resources/ (leiningen project). When jar'd/uberjar'd, this foo/ directory is put at the root of the jar. As files inside jar may not be physically consistent at runtime, you can't use basic copy function to recursively copy the directory to the outside world.

Several solutions for the Java world exist (How to write a Java program which can extract a JAR file and store its data in specified directory (location)? and How to extract directory (and sub directories) from jar resource? for example), but I didn't find any existing solution for Clojure. As a beginner (both Clojure and Java), I'm not sure how to translate above solutions to Clojure. Translating literally line by line from Java to Clojurish Java Interop doesn't seem right. Is there an "official", clojure-idiomatic way to do this ?

Note that I'm using Raynes' fs utilities library. There doesn't seem to be a function to do this directly, but maybe there's some elements I can use to simplify the process ? (besides the obvious basic io sugar)

like image 226
Logan Braga Avatar asked Feb 21 '15 11:02

Logan Braga


3 Answers

I've written cpath-clj a few months back that will list resources on the classpath by URI. You could then try the following:

(require '[cpath-clj.core :as cp]
         '[clojure.java.io :as io])

(doseq [[path uris] (cp/resources (io/resource "foo"))
        :let [uri (first uris)
              relative-path (subs path 1)
              output-file (io/file output-directory relative-path)]]
  (with-open [in (io/input-stream uri)]
    (io/copy in output-file)))

There is some juggling going on since the library was not written with this use case in mind:

  • (cp/resources (io/resource "foo")) will give you the contents of your foo directory - if you had only used (cp/resources "foo") all such directories on the classpath would have been found,
  • theoretically, there can be multiple files with the same path on the classpath, that's why the function returns multiple uris; in our case, only the first one is of interest.
  • path is always starting with a slash, so to get the relative one, we have to remove it.

Maybe that's helpful to you.

like image 174
xsc Avatar answered Oct 16 '22 16:10

xsc


(defn copy-resource! [resource-filename]
  (let [resource-file (io/resource resource-filename)
        tmp-file (io/file "/tmp" resource-filename)]
    (with-open [in (io/input-stream resource-file)] (io/copy in tmp-file))))

(copy-resource! "my-logo.png")
like image 37
Kris Avatar answered Oct 16 '22 16:10

Kris


After trying around some more and getting help from #clojure, I was able to came up with a "kind-of-clojurish-i-guess" translation from my previous first link :

(ns my-ns
  (:require [me.raynes.fs    :as f])
  (:import [java.util.jar JarFile JarEntry]))

(defn extract-dir-from-jar
  "Takes the string path of a jar, a dir name inside that jar and a destination
  dir, and copies the from dir to the to dir."
  [^String jar-dir from to]
  (let [jar (JarFile. jar-dir)]
    (doseq [^JarEntry file (enumeration-seq (.entries jar))]
      (if (.startsWith (.getName file) from)
        (let [f (f/file to (.getName file))]
          (if (.isDirectory file)
            (f/mkdir f)
            (do (f/mkdirs (f/parent f))
                (with-open [is (.getInputStream jar file)
                            os (io/output-stream f)]
                  (io/copy is os)))))))))

I guess I can tidy this up a bit (especially around the .startsWith bit) but at least that works perfectly.

like image 1
Logan Braga Avatar answered Oct 16 '22 16:10

Logan Braga