Version 4.3.0: unzip, better list comparison mess.
[fanfix.git] / src / be / nikiroo / utils / IOUtils.java
CommitLineData
ec1f3444
NR
1package be.nikiroo.utils;
2
3import java.io.BufferedReader;
80500544 4import java.io.ByteArrayOutputStream;
ec1f3444
NR
5import java.io.File;
6import java.io.FileInputStream;
7import java.io.FileOutputStream;
ec1f3444
NR
8import java.io.FileWriter;
9import java.io.IOException;
10import java.io.InputStream;
3f8349b7 11import java.io.InputStreamReader;
ec1f3444 12import java.io.OutputStream;
59864f77
NR
13import java.util.ArrayList;
14import java.util.List;
ec1f3444 15import java.util.zip.ZipEntry;
4110f63b 16import java.util.zip.ZipInputStream;
ec1f3444
NR
17import java.util.zip.ZipOutputStream;
18
19/**
72c32e88 20 * This class offer some utilities based around Streams.
ec1f3444
NR
21 *
22 * @author niki
23 */
24public class IOUtils {
25 /**
26 * Write the data to the given {@link File}.
27 *
28 * @param in
29 * the data source
30 * @param target
31 * the target {@link File}
32 *
33 * @throws IOException
34 * in case of I/O error
35 */
36 public static void write(InputStream in, File target) throws IOException {
37 OutputStream out = new FileOutputStream(target);
38 try {
39 write(in, out);
40 } finally {
41 out.close();
42 }
43 }
44
45 /**
46 * Write the data to the given {@link OutputStream}.
47 *
48 * @param in
49 * the data source
db31c358 50 * @param out
ec1f3444
NR
51 * the target {@link OutputStream}
52 *
53 * @throws IOException
54 * in case of I/O error
55 */
56 public static void write(InputStream in, OutputStream out)
57 throws IOException {
58 byte buffer[] = new byte[4069];
59 for (int len = 0; (len = in.read(buffer)) > 0;) {
60 out.write(buffer, 0, len);
61 }
62 }
63
64 /**
65 * Recursively Add a {@link File} (which can thus be a directory, too) to a
66 * {@link ZipOutputStream}.
67 *
68 * @param zip
69 * the stream
70 * @param base
71 * the path to prepend to the ZIP info before the actual
72 * {@link File} path
73 * @param target
74 * the source {@link File} (which can be a directory)
75 * @param targetIsRoot
76 * FALSE if we need to add a {@link ZipEntry} for base/target,
77 * TRUE to add it at the root of the ZIP
78 *
79 * @throws IOException
80 * in case of I/O error
81 */
82 public static void zip(ZipOutputStream zip, String base, File target,
83 boolean targetIsRoot) throws IOException {
84 if (target.isDirectory()) {
85 if (!targetIsRoot) {
86 if (base == null || base.isEmpty()) {
87 base = target.getName();
88 } else {
89 base += "/" + target.getName();
90 }
91 zip.putNextEntry(new ZipEntry(base + "/"));
92 }
0988831f
NR
93
94 File[] files = target.listFiles();
95 if (files != null) {
96 for (File file : files) {
97 zip(zip, base, file, false);
98 }
ec1f3444
NR
99 }
100 } else {
101 if (base == null || base.isEmpty()) {
102 base = target.getName();
103 } else {
104 base += "/" + target.getName();
105 }
106 zip.putNextEntry(new ZipEntry(base));
107 FileInputStream in = new FileInputStream(target);
108 try {
109 IOUtils.write(in, zip);
110 } finally {
111 in.close();
112 }
113 }
114 }
115
116 /**
117 * Zip the given source into dest.
118 *
119 * @param src
120 * the source {@link File} (which can be a directory)
121 * @param dest
122 * the destination <tt>.zip</tt> file
db31c358 123 * @param srcIsRoot
ec1f3444
NR
124 * FALSE if we need to add a {@link ZipEntry} for src, TRUE to
125 * add it at the root of the ZIP
126 *
127 * @throws IOException
128 * in case of I/O error
129 */
130 public static void zip(File src, File dest, boolean srcIsRoot)
131 throws IOException {
132 OutputStream out = new FileOutputStream(dest);
133 try {
134 ZipOutputStream zip = new ZipOutputStream(out);
135 try {
136 IOUtils.zip(zip, "", src, srcIsRoot);
137 } finally {
138 zip.close();
139 }
140 } finally {
141 out.close();
142 }
143 }
144
4110f63b
NR
145 /**
146 * Unzip the given ZIP file into the target directory.
147 *
148 * @param zipFile
149 * the ZIP file
150 * @param targetDirectory
151 * the target directory
152 *
153 * @return the number of extracted files (not directories)
154 *
155 * @throws IOException
156 * in case of I/O errors
157 */
158 public static long unzip(File zipFile, File targetDirectory)
159 throws IOException {
160 long count = 0;
161
162 if (targetDirectory.exists() && targetDirectory.isFile()) {
163 throw new IOException("Cannot unzip " + zipFile + " into "
164 + targetDirectory + ": it is not a directory");
165 }
166
167 targetDirectory.mkdir();
168 if (!targetDirectory.exists()) {
169 throw new IOException("Cannot create target directory "
170 + targetDirectory);
171 }
172
173 FileInputStream in = new FileInputStream(zipFile);
174 try {
175 ZipInputStream zipStream = new ZipInputStream(in);
176 try {
177 for (ZipEntry entry = zipStream.getNextEntry(); entry != null; entry = zipStream
178 .getNextEntry()) {
179 File file = new File(targetDirectory, entry.getName());
180 if (entry.isDirectory()) {
181 file.mkdirs();
182 } else {
183 IOUtils.write(zipStream, file);
184 count++;
185 }
186 }
187 } finally {
188 zipStream.close();
189 }
190 } finally {
191 in.close();
192 }
193
194 return count;
195 }
196
ec1f3444
NR
197 /**
198 * Write the {@link String} content to {@link File}.
199 *
200 * @param dir
201 * the directory where to write the {@link File}
202 * @param filename
203 * the {@link File} name
204 * @param content
205 * the content
206 *
207 * @throws IOException
208 * in case of I/O error
209 */
210 public static void writeSmallFile(File dir, String filename, String content)
211 throws IOException {
212 if (!dir.exists()) {
213 dir.mkdirs();
214 }
215
216 FileWriter writerVersion = new FileWriter(new File(dir, filename));
217 try {
218 writerVersion.write(content);
219 } finally {
220 writerVersion.close();
221 }
222 }
223
224 /**
225 * Read the whole {@link File} content into a {@link String}.
226 *
227 * @param file
228 * the {@link File}
229 *
230 * @return the content
231 *
232 * @throws IOException
233 * in case of I/O error
234 */
235 public static String readSmallFile(File file) throws IOException {
3f8349b7
NR
236 InputStream stream = new FileInputStream(file);
237 try {
238 return readSmallStream(stream);
239 } finally {
240 stream.close();
241 }
242 }
243
244 /**
245 * Read the whole {@link InputStream} content into a {@link String}.
246 *
247 * @param stream
248 * the {@link InputStream}
249 *
250 * @return the content
251 *
252 * @throws IOException
253 * in case of I/O error
254 */
255 public static String readSmallStream(InputStream stream) throws IOException {
530d4062
NR
256 // do NOT close the reader, or the related stream will be closed, too
257 // reader.close();
3f8349b7
NR
258 BufferedReader reader = new BufferedReader(
259 new InputStreamReader(stream));
530d4062
NR
260
261 StringBuilder builder = new StringBuilder();
262 for (String line = reader.readLine(); line != null; line = reader
263 .readLine()) {
264 builder.append(line);
265 builder.append("\n");
ec1f3444 266 }
530d4062
NR
267
268 return builder.toString();
ec1f3444
NR
269 }
270
59864f77
NR
271 /**
272 * Recursively delete the given {@link File}, which may of course also be a
273 * directory.
274 * <p>
275 * Will either silently continue or throw an exception in case of error,
276 * depending upon the parameters.
277 *
278 * @param target
279 * the target to delete
280 * @param exception
281 * TRUE to throw an {@link IOException} in case of error, FALSE
282 * to silently continue
283 *
284 * @return TRUE if all files were deleted, FALSE if an error occurred
285 *
286 * @throws IOException
287 * if an error occurred and the parameters allow an exception to
288 * be thrown
289 */
290 public static boolean deltree(File target, boolean exception)
291 throws IOException {
292 List<File> list = deltree(target, null);
293 if (exception && !list.isEmpty()) {
0988831f 294 StringBuilder slist = new StringBuilder();
59864f77 295 for (File file : list) {
0988831f 296 slist.append("\n").append(file.getPath());
59864f77
NR
297 }
298
299 throw new IOException("Cannot delete all the files from: <" //
0988831f 300 + target + ">:" + slist.toString());
59864f77
NR
301 }
302
303 return list.isEmpty();
304 }
305
ec1f3444
NR
306 /**
307 * Recursively delete the given {@link File}, which may of course also be a
308 * directory.
309 * <p>
310 * Will silently continue in case of error.
311 *
312 * @param target
313 * the target to delete
59864f77
NR
314 *
315 * @return TRUE if all files were deleted, FALSE if an error occurred
316 */
317 public static boolean deltree(File target) {
318 return deltree(target, null).isEmpty();
319 }
320
321 /**
322 * Recursively delete the given {@link File}, which may of course also be a
323 * directory.
324 * <p>
325 * Will collect all {@link File} that cannot be deleted in the given
326 * accumulator.
327 *
328 * @param target
329 * the target to delete
330 * @param errorAcc
331 * the accumulator to use for errors, or NULL to create a new one
332 *
333 * @return the errors accumulator
ec1f3444 334 */
59864f77
NR
335 public static List<File> deltree(File target, List<File> errorAcc) {
336 if (errorAcc == null) {
337 errorAcc = new ArrayList<File>();
338 }
339
7ee9568b
NR
340 File[] files = target.listFiles();
341 if (files != null) {
342 for (File file : files) {
59864f77 343 errorAcc = deltree(file, errorAcc);
ec1f3444
NR
344 }
345 }
346
347 if (!target.delete()) {
59864f77 348 errorAcc.add(target);
ec1f3444 349 }
59864f77
NR
350
351 return errorAcc;
ec1f3444 352 }
b607df60 353
16d59378
NR
354 /**
355 * Open the given /-separated resource (from the binary root).
356 *
357 * @param name
358 * the resource name
359 *
360 * @return the opened resource if found, NLL if not
361 */
362 public static InputStream openResource(String name) {
363 ClassLoader loader = IOUtils.class.getClassLoader();
364 if (loader == null) {
365 loader = ClassLoader.getSystemClassLoader();
366 }
367
368 return loader.getResourceAsStream(name);
369 }
80500544
NR
370
371 /**
372 * Return a resetable {@link InputStream} from this stream, and reset it.
373 *
374 * @param in
375 * the input stream
376 * @return the resetable stream, which <b>may</b> be the same
377 *
378 * @throws IOException
379 * in case of I/O error
380 */
381 public static InputStream forceResetableStream(InputStream in)
382 throws IOException {
383 MarkableFileInputStream tmpIn = null;
384 File tmp = null;
385
386 boolean resetable = in.markSupported();
387 if (resetable) {
388 try {
389 in.reset();
390 } catch (IOException e) {
391 resetable = false;
392 }
393 }
394
395 if (resetable) {
396 return in;
397 }
398
399 tmp = File.createTempFile(".tmp-stream", ".tmp");
400 try {
401 write(in, tmp);
402 tmpIn = new MarkableFileInputStream(new FileInputStream(tmp));
403 return tmpIn;
404 } finally {
405 try {
406 if (tmpIn != null) {
407 tmpIn.close();
408 }
409 } finally {
410 tmp.delete();
411 }
412 }
413 }
414
415 /**
416 * Convert the {@link InputStream} into a byte array.
417 *
418 * @param in
419 * the input stream
420 *
421 * @return the array
422 *
423 * @throws IOException
424 * in case of I/O error
425 */
426 public static byte[] toByteArray(InputStream in) throws IOException {
427 ByteArrayOutputStream out = new ByteArrayOutputStream();
428 write(in, out);
429
430 byte[] array = out.toByteArray();
431 out.close();
432
433 return array;
434 }
ec1f3444 435}