-
Notifications
You must be signed in to change notification settings - Fork 1
/
zip.clj
159 lines (137 loc) · 6.76 KB
/
zip.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(ns com.eldrix.trud.impl.zip
(:require [clojure.java.io :as io]
[clojure.string :as str])
(:import (java.io File FileOutputStream)
(java.nio.file Files Path Paths)
(java.nio.file.attribute FileAttribute)
(java.util.zip ZipEntry ZipInputStream ZipOutputStream)
(java.util.regex Pattern)))
(set! *warn-on-reflection* true)
(defn unzip
"Unzip a zip archive to the directory specified.
Parameters:
- in : path of zip file
- out : path of the directory to which files will be extracted.
If no `out` path is specified, a temporary directory will be created.
The out directory will be created if it doesn't exist."
([^Path in] (unzip in nil))
([^Path in ^Path out]
(let [out-path (if out (Files/createDirectories out (make-array FileAttribute 0)) ;; this will be a NOP if directory already exists
(Files/createTempDirectory "trud" (make-array FileAttribute 0)))]
(with-open [input (ZipInputStream. (io/input-stream (.toFile in)))]
(loop [entry (.getNextEntry input)]
(if-not entry
out-path
(do (if (.isDirectory entry)
(Files/createDirectories (.resolve out-path (.getName entry)) (make-array FileAttribute 0))
(let [new-file (.toFile (.resolve out-path (.getName entry)))
parent (.getParentFile new-file)]
(when-not (.isDirectory parent) (.mkdirs parent)) ;; if parent directory doesn't exist, create
(io/copy input (.toFile (.resolve out-path (.getName entry))))))
(recur (.getNextEntry input)))))))))
(defn is-zip-file? [s]
(str/ends-with? (str/lower-case s) ".zip"))
(declare unzip-in-place)
(defn unzip-nested
"Unzip a zip archive to the directory specified, unzipping nested zip files.
Parameters:
- in : path of zip file
- out : path of the directory to which files will be extracted.
If no `out` path is specified, a temporary directory will be created.
The out directory will be created if it doesn't exist."
([^Path in] (unzip-nested in nil))
([^Path in ^Path out]
(let [out-path (if out (Files/createDirectories out (make-array FileAttribute 0)) ;; this will be a NOP if directory already exists
(Files/createTempDirectory "trud" (make-array FileAttribute 0)))]
(with-open [input (ZipInputStream. (io/input-stream (.toFile in)))]
(loop [entry (.getNextEntry input)
nested []]
(if-not entry
(do
(unzip-in-place nested)
out-path)
(do (if (.isDirectory entry)
(Files/createDirectories (.resolve out-path (.getName entry)) (make-array FileAttribute 0))
(let [new-file (.toFile (.resolve out-path (.getName entry)))
parent (.getParentFile new-file)]
(when-not (.isDirectory parent) (.mkdirs parent)) ;; if parent directory doesn't exist, create
(io/copy input (.toFile (.resolve out-path (.getName entry))))))
(recur (.getNextEntry input)
(if (is-zip-file? (.getName entry)) (conj nested (.resolve out-path (.getName entry))) nested)))))))))
(defn unzip-in-place
"Unzip each path at the same location, creating a directory based on the name
of the zip file, replacing any '.' in the filename with '_'. This doesn't
check that this won't clobber an existing file.
Parameters:
- paths: a sequence of objects of type `java.nio.file.Path`."
[paths]
(doseq [^Path path paths]
(let [n (.toString (.getFileName path))
n' (str/replace n #"\." "-")]
(unzip path (.resolve (.getParent path) ^String n')))))
(defn unzip-query
"Resolves a query representing files from a nested directory structure,
including extracting nested zip files.
A query is a potentially nested vector of strings or paths.
[\"test.zip\"
[\"nested1.zip\"]
[\"nested2.zip\" \"file.txt\"]]
This will extract the test.zip file, extract the files in both nested1.zip
and nested2.zip and also returns a path for `file.txt` from the nested2.zip.
Results will be java.nio.file.Path objects in the same shape as the query.
For the example above, four paths will be returned."
([q] (unzip-query nil q))
([^Path p q]
(cond
;; turn a string query into a path and re-run
(string? q)
(unzip-query (if p (.resolve p ^String q) (Paths/get q (make-array String 0))))
;; process a vector by resolving (perhaps unzipping?) first item and resolving each subsequent in context
(vector? q) (let [unzipped (unzip-query p (first q))]
(apply conj [unzipped] (map #(unzip-query unzipped %) (rest q))))
;; process a path depending on the file type
(instance? Path q)
(if (str/ends-with? (str/lower-case (str q)) ".zip") ; - unzip zip files and return extracted files
(unzip q)
(if p (.resolve p ^Path q) q))
;; turn a regexp into a vector of filenames matching that regexp
(and p (instance? Pattern q))
(->> (file-seq (.toFile p)) ;; sequence; each a java.io.File
(map #(.toPath ^File %)) ;; convert each to a java.nio.Path
(map #(.relativize p %)) ;; generate a relative path
(filter #(re-matches q (str %))) ;; filter matching based on path
(map #(.resolve p ^Path %))) ;; finally, back to an absolute path
;; return an empty slot if we haven't been able to resolve anything sensible
:else
nil)))
(defn delete-files
"Delete all files from the `dir` specified, where `dir` can be anything
coercible to a `file` using [[clojure.java.io/as-file]]."
[dir]
(doseq [f (file-seq dir)]
(io/delete-file f :silently true)))
(defn delete-paths
"Delete all paths specified, including nested structures.
Parameters:
- paths : a sequence of objects `java.nio.file.Path`."
[paths]
(doseq [path (flatten paths)]
(delete-files (.toFile ^Path path))))
(defn zip
"Zip a file or directory, either to a temporary file, or the named file.
Returns `java.io.File` for the created zip file."
([f]
(zip (.toFile (Files/createTempFile nil "zip" (make-array FileAttribute 0))) f))
([zip-file f]
(let [zf (io/file zip-file)]
(with-open [fos (FileOutputStream. zf)
zos (ZipOutputStream. fos)]
(let [f' (io/file f)
root-path (.getParent (.toAbsolutePath (.toPath f')))]
(doseq [^File f'' (filter #(.isFile ^File %) (file-seq f'))]
(let [n (str (.relativize root-path (.toAbsolutePath (.toPath f''))))]
(.putNextEntry zos (ZipEntry. n))
(io/copy f'' zos)
(.closeEntry zos)))
zf)))))
(comment)