17 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
185 |
ilm |
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
17 |
ilm |
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
package org.openconcerto.utils;
|
|
|
15 |
|
180 |
ilm |
16 |
import org.openconcerto.utils.cc.ITransformerExn;
|
|
|
17 |
|
17 |
ilm |
18 |
import java.io.BufferedInputStream;
|
|
|
19 |
import java.io.BufferedOutputStream;
|
|
|
20 |
import java.io.ByteArrayInputStream;
|
142 |
ilm |
21 |
import java.io.ByteArrayOutputStream;
|
180 |
ilm |
22 |
import java.io.Closeable;
|
17 |
ilm |
23 |
import java.io.File;
|
|
|
24 |
import java.io.FileInputStream;
|
|
|
25 |
import java.io.FileNotFoundException;
|
|
|
26 |
import java.io.FileOutputStream;
|
|
|
27 |
import java.io.IOException;
|
|
|
28 |
import java.io.InputStream;
|
|
|
29 |
import java.io.OutputStream;
|
142 |
ilm |
30 |
import java.nio.ByteBuffer;
|
17 |
ilm |
31 |
import java.util.Enumeration;
|
|
|
32 |
import java.util.Set;
|
180 |
ilm |
33 |
import java.util.function.Function;
|
|
|
34 |
import java.util.jar.JarEntry;
|
|
|
35 |
import java.util.jar.JarOutputStream;
|
17 |
ilm |
36 |
import java.util.zip.CRC32;
|
142 |
ilm |
37 |
import java.util.zip.DeflaterOutputStream;
|
|
|
38 |
import java.util.zip.InflaterInputStream;
|
17 |
ilm |
39 |
import java.util.zip.ZipEntry;
|
|
|
40 |
import java.util.zip.ZipException;
|
|
|
41 |
import java.util.zip.ZipOutputStream;
|
|
|
42 |
|
|
|
43 |
/**
|
|
|
44 |
* Permet d'écrire dans un fichier zip.
|
|
|
45 |
*
|
|
|
46 |
* @author ILM Informatique
|
|
|
47 |
* @see org.openconcerto.utils.Unzip
|
|
|
48 |
*/
|
180 |
ilm |
49 |
public class Zip implements Closeable {
|
17 |
ilm |
50 |
|
142 |
ilm |
51 |
static public byte[] deflate(final String s) throws IOException {
|
|
|
52 |
return deflate(s.getBytes(StringUtils.UTF8));
|
|
|
53 |
}
|
|
|
54 |
|
|
|
55 |
static public byte[] deflate(final byte[] b) throws IOException {
|
|
|
56 |
final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
|
|
57 |
final DeflaterOutputStream out = new DeflaterOutputStream(bOut);
|
|
|
58 |
out.write(b);
|
|
|
59 |
out.close();
|
|
|
60 |
return bOut.toByteArray();
|
|
|
61 |
}
|
|
|
62 |
|
|
|
63 |
static public ByteBuffer deflateToBuffer(final String s) throws IOException {
|
|
|
64 |
return ByteBuffer.wrap(deflate(s));
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
static public String inflateToString(final ByteBuffer payload) throws IOException {
|
|
|
68 |
return inflateToString(payload, payload.remaining());
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
static public String inflateToString(final ByteBuffer payload, final int length) throws IOException {
|
|
|
72 |
final byte[] b = new byte[length];
|
|
|
73 |
payload.get(b);
|
|
|
74 |
return inflateToString(b);
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
static public String inflateToString(final byte[] b) throws IOException {
|
|
|
78 |
return new String(inflate(b), StringUtils.UTF8);
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
static public byte[] inflate(final byte[] b) throws IOException {
|
|
|
82 |
final InflaterInputStream in = new InflaterInputStream(new ByteArrayInputStream(b));
|
|
|
83 |
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
84 |
StreamUtils.copy(in, out);
|
|
|
85 |
out.close();
|
|
|
86 |
return out.toByteArray();
|
|
|
87 |
}
|
|
|
88 |
|
17 |
ilm |
89 |
/**
|
|
|
90 |
* Copie de from dans to seulement les entrées dont le nom n'est pas dans
|
|
|
91 |
* <code>excludedEntries</code>.
|
|
|
92 |
*
|
|
|
93 |
* @param from le zip source.
|
|
|
94 |
* @param excludedEntries les noms des entrées à exclure.
|
|
|
95 |
* @param to le zip de destination, s'il existe déjà les entrées de from seront ajoutées aux
|
|
|
96 |
* existantes.
|
|
|
97 |
* @return le fichier zip dest.
|
|
|
98 |
* @throws ZipException
|
|
|
99 |
* @throws IOException
|
|
|
100 |
*/
|
142 |
ilm |
101 |
static public Zip createFrom(File from, File to, Set<String> excludedEntries) throws ZipException, IOException {
|
17 |
ilm |
102 |
Unzip unz = new Unzip(from);
|
|
|
103 |
Zip res = new Zip(to);
|
142 |
ilm |
104 |
final Enumeration<? extends ZipEntry> en = unz.entries();
|
17 |
ilm |
105 |
while (en.hasMoreElements()) {
|
142 |
ilm |
106 |
final ZipEntry entry = en.nextElement();
|
17 |
ilm |
107 |
if (!excludedEntries.contains(entry.getName())) {
|
|
|
108 |
res.zip(entry.getName(), unz.getInputStream(entry));
|
|
|
109 |
}
|
|
|
110 |
}
|
|
|
111 |
unz.close();
|
|
|
112 |
return res;
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
/**
|
|
|
116 |
* Efface les entrées spécifées de src. Si dest existe, il sera ecrasé.
|
|
|
117 |
*
|
|
|
118 |
* @param src le zip source.
|
|
|
119 |
* @param entriesName les noms des entrées à effacer.
|
|
|
120 |
* @param dest le zip de destination.
|
|
|
121 |
* @throws ZipException
|
|
|
122 |
* @throws IOException
|
|
|
123 |
*/
|
142 |
ilm |
124 |
static public void delete(File src, Set<String> entriesName, File dest) throws ZipException, IOException {
|
17 |
ilm |
125 |
if (dest.exists())
|
|
|
126 |
dest.delete();
|
|
|
127 |
createFrom(src, dest, entriesName).close();
|
|
|
128 |
}
|
|
|
129 |
|
180 |
ilm |
130 |
static public Zip createJar(final OutputStream out) {
|
|
|
131 |
return new Zip(out, JarOutputStream::new, JarEntry::new);
|
|
|
132 |
}
|
|
|
133 |
|
17 |
ilm |
134 |
// *** Instance
|
|
|
135 |
|
|
|
136 |
private final OutputStream outstream;
|
180 |
ilm |
137 |
private final ITransformerExn<OutputStream, ZipOutputStream, IOException> createZipStream;
|
17 |
ilm |
138 |
private ZipOutputStream zos;
|
180 |
ilm |
139 |
private final Function<String, ZipEntry> createEntry;
|
17 |
ilm |
140 |
// is an entry open, ie addEntry() has been called but closeEntry() not yet
|
|
|
141 |
private boolean entryOpen;
|
|
|
142 |
|
|
|
143 |
/**
|
|
|
144 |
* Construit un fichier zip. ATTN Le fichier passé sera écrasé lors de la première écriture.
|
|
|
145 |
*
|
|
|
146 |
* @param f le fichier dans lequel sauver, peut ne pas exister.
|
|
|
147 |
* @throws FileNotFoundException if f cannot be written to.
|
|
|
148 |
*/
|
|
|
149 |
public Zip(File f) throws FileNotFoundException {
|
|
|
150 |
this(new FileOutputStream(f));
|
|
|
151 |
}
|
|
|
152 |
|
|
|
153 |
/**
|
|
|
154 |
* Construit un fichier zip.
|
|
|
155 |
*
|
|
|
156 |
* @param out un stream dans lequel écrire.
|
|
|
157 |
*/
|
|
|
158 |
public Zip(OutputStream out) {
|
180 |
ilm |
159 |
this(out, ZipOutputStream::new, ZipEntry::new);
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
public Zip(OutputStream out, final ITransformerExn<OutputStream, ZipOutputStream, IOException> createZipStream, final Function<String, ZipEntry> createEntry) {
|
17 |
ilm |
163 |
this.outstream = out;
|
180 |
ilm |
164 |
this.createZipStream = createZipStream;
|
17 |
ilm |
165 |
this.zos = null;
|
180 |
ilm |
166 |
this.createEntry = createEntry;
|
17 |
ilm |
167 |
this.entryOpen = false;
|
|
|
168 |
}
|
|
|
169 |
|
180 |
ilm |
170 |
@Override
|
17 |
ilm |
171 |
public synchronized void close() throws IOException {
|
|
|
172 |
if (this.zos != null) {
|
|
|
173 |
// ferme aussi le FileOutputStream
|
|
|
174 |
this.zos.close();
|
180 |
ilm |
175 |
} else {
|
|
|
176 |
this.outstream.close();
|
17 |
ilm |
177 |
}
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
// *** Ecriture
|
|
|
181 |
|
180 |
ilm |
182 |
private synchronized ZipOutputStream getOutStream() throws IOException {
|
17 |
ilm |
183 |
if (this.zos == null) {
|
180 |
ilm |
184 |
this.zos = this.createZipStream.transformChecked(this.outstream);
|
17 |
ilm |
185 |
}
|
|
|
186 |
return this.zos;
|
|
|
187 |
}
|
|
|
188 |
|
180 |
ilm |
189 |
public ZipEntry createEntry(String name) {
|
|
|
190 |
return this.createEntry.apply(name);
|
|
|
191 |
}
|
|
|
192 |
|
17 |
ilm |
193 |
/**
|
|
|
194 |
* Ajoute newFile dans ce fichier. Il sera enregistré dans le zip directement à la racine.
|
|
|
195 |
*
|
|
|
196 |
* @param newFile le fichier à ajouter.
|
|
|
197 |
* @throws IOException si le fichier ne peut etre zippé.
|
|
|
198 |
*/
|
|
|
199 |
public void zip(File newFile) throws IOException {
|
|
|
200 |
// on ne garde que la derniere partie du chemin
|
180 |
ilm |
201 |
this.zip(newFile.getName(), newFile);
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
public void zip(final String entryName, final File newFile) throws IOException {
|
|
|
205 |
final ZipEntry entry = this.createEntry(entryName);
|
|
|
206 |
// TODO call ZipEntry.setCreationTime()
|
|
|
207 |
entry.setTime(newFile.lastModified());
|
177 |
ilm |
208 |
try (final BufferedInputStream ins = new BufferedInputStream(new FileInputStream(newFile))) {
|
180 |
ilm |
209 |
this.zip(entry, ins);
|
177 |
ilm |
210 |
}
|
17 |
ilm |
211 |
}
|
|
|
212 |
|
|
|
213 |
/**
|
|
|
214 |
* Zippe le contenu de <code>in</code>.
|
|
|
215 |
*
|
|
|
216 |
* @param name le nom de l'entrée.
|
|
|
217 |
* @param in l'ajout.
|
|
|
218 |
* @throws IOException si in ne peut etre zippé.
|
|
|
219 |
*/
|
180 |
ilm |
220 |
public void zip(String name, InputStream in) throws IOException {
|
|
|
221 |
this.zip(this.createEntry(name), in);
|
|
|
222 |
}
|
17 |
ilm |
223 |
|
180 |
ilm |
224 |
public synchronized void zip(final ZipEntry entry, InputStream in) throws IOException {
|
|
|
225 |
this.putNextEntry(entry);
|
|
|
226 |
|
17 |
ilm |
227 |
byte b[] = new byte[512];
|
|
|
228 |
int len = 0;
|
|
|
229 |
while ((len = in.read(b)) != -1) {
|
|
|
230 |
this.getOutStream().write(b, 0, len);
|
|
|
231 |
}
|
|
|
232 |
|
|
|
233 |
this.closeEntry();
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
public void zip(String name, byte[] in, final boolean compressed) throws IOException {
|
|
|
237 |
// don't make #zip(String, InputStream) call this method, it would require to read the
|
|
|
238 |
// entire stream into memory
|
|
|
239 |
if (compressed)
|
|
|
240 |
this.zip(name, new ByteArrayInputStream(in));
|
|
|
241 |
else
|
|
|
242 |
this.zipNonCompressed(name, in);
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
/**
|
|
|
246 |
* Zip the passed array with the {@link ZipEntry#STORED} method. This method takes care of the
|
|
|
247 |
* CRC and size.
|
|
|
248 |
*
|
|
|
249 |
* @param name the entry name.
|
|
|
250 |
* @param in what to zip.
|
|
|
251 |
* @throws IOException if an error occurs.
|
|
|
252 |
*/
|
|
|
253 |
public synchronized void zipNonCompressed(String name, byte[] in) throws IOException {
|
180 |
ilm |
254 |
final ZipEntry entry = createEntry(name);
|
17 |
ilm |
255 |
entry.setMethod(ZipEntry.STORED);
|
|
|
256 |
final CRC32 crc = new CRC32();
|
|
|
257 |
crc.update(in);
|
|
|
258 |
entry.setCrc(crc.getValue());
|
|
|
259 |
entry.setSize(in.length);
|
|
|
260 |
|
|
|
261 |
this.putNextEntry(entry);
|
|
|
262 |
this.getOutStream().write(in);
|
|
|
263 |
this.closeEntry();
|
|
|
264 |
}
|
|
|
265 |
|
|
|
266 |
/**
|
|
|
267 |
* Adds a new entry to this zip file. ATTN you must close the returned stream before you can add
|
|
|
268 |
* to this zip again.
|
|
|
269 |
*
|
|
|
270 |
* @param name the name of the entry.
|
|
|
271 |
* @return a stream to write to the entry.
|
|
|
272 |
* @throws IOException if a pb occurs.
|
|
|
273 |
*/
|
180 |
ilm |
274 |
public synchronized OutputStream createEntryStream(String name) throws IOException {
|
17 |
ilm |
275 |
this.putNextEntry(name);
|
|
|
276 |
return new BufferedOutputStream(this.getOutStream()) {
|
|
|
277 |
public void close() throws IOException {
|
|
|
278 |
this.flush();
|
|
|
279 |
Zip.this.closeEntry();
|
|
|
280 |
}
|
|
|
281 |
};
|
|
|
282 |
}
|
|
|
283 |
|
|
|
284 |
private final synchronized void putNextEntry(String name) throws IOException, FileNotFoundException {
|
180 |
ilm |
285 |
this.putNextEntry(createEntry(name));
|
17 |
ilm |
286 |
}
|
|
|
287 |
|
|
|
288 |
private final synchronized void putNextEntry(ZipEntry entry) throws IOException, FileNotFoundException {
|
|
|
289 |
if (this.entryOpen)
|
|
|
290 |
throw new IllegalStateException("previous entry not closed");
|
|
|
291 |
this.entryOpen = true;
|
|
|
292 |
this.getOutStream().putNextEntry(entry);
|
|
|
293 |
}
|
|
|
294 |
|
|
|
295 |
protected final synchronized void closeEntry() throws IOException {
|
|
|
296 |
this.getOutStream().closeEntry();
|
|
|
297 |
this.entryOpen = false;
|
|
|
298 |
}
|
|
|
299 |
|
|
|
300 |
}
|