Version 1.4.1: Progress fixes
[fanfix.git] / src / be / nikiroo / utils / IOUtils.java
CommitLineData
ec1f3444
NR
1package be.nikiroo.utils;
2
b607df60
NR
3import java.awt.Image;
4import java.awt.geom.AffineTransform;
5import java.awt.image.AffineTransformOp;
6import java.awt.image.BufferedImage;
ec1f3444
NR
7import java.io.BufferedReader;
8import java.io.File;
9import java.io.FileInputStream;
10import java.io.FileOutputStream;
11import java.io.FileReader;
12import java.io.FileWriter;
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.OutputStream;
ec1f3444
NR
16import java.util.zip.ZipEntry;
17import java.util.zip.ZipOutputStream;
18
b607df60
NR
19import javax.imageio.ImageIO;
20
ec1f3444 21/**
72c32e88 22 * This class offer some utilities based around Streams.
ec1f3444
NR
23 *
24 * @author niki
25 */
26public class IOUtils {
27 /**
28 * Write the data to the given {@link File}.
29 *
30 * @param in
31 * the data source
32 * @param target
33 * the target {@link File}
34 *
35 * @throws IOException
36 * in case of I/O error
37 */
38 public static void write(InputStream in, File target) throws IOException {
39 OutputStream out = new FileOutputStream(target);
40 try {
41 write(in, out);
42 } finally {
43 out.close();
44 }
45 }
46
47 /**
48 * Write the data to the given {@link OutputStream}.
49 *
50 * @param in
51 * the data source
52 * @param target
53 * the target {@link OutputStream}
54 *
55 * @throws IOException
56 * in case of I/O error
57 */
58 public static void write(InputStream in, OutputStream out)
59 throws IOException {
60 byte buffer[] = new byte[4069];
61 for (int len = 0; (len = in.read(buffer)) > 0;) {
62 out.write(buffer, 0, len);
63 }
64 }
65
66 /**
67 * Recursively Add a {@link File} (which can thus be a directory, too) to a
68 * {@link ZipOutputStream}.
69 *
70 * @param zip
71 * the stream
72 * @param base
73 * the path to prepend to the ZIP info before the actual
74 * {@link File} path
75 * @param target
76 * the source {@link File} (which can be a directory)
77 * @param targetIsRoot
78 * FALSE if we need to add a {@link ZipEntry} for base/target,
79 * TRUE to add it at the root of the ZIP
80 *
81 * @throws IOException
82 * in case of I/O error
83 */
84 public static void zip(ZipOutputStream zip, String base, File target,
85 boolean targetIsRoot) throws IOException {
86 if (target.isDirectory()) {
87 if (!targetIsRoot) {
88 if (base == null || base.isEmpty()) {
89 base = target.getName();
90 } else {
91 base += "/" + target.getName();
92 }
93 zip.putNextEntry(new ZipEntry(base + "/"));
94 }
95 for (File file : target.listFiles()) {
96 zip(zip, base, file, false);
97 }
98 } else {
99 if (base == null || base.isEmpty()) {
100 base = target.getName();
101 } else {
102 base += "/" + target.getName();
103 }
104 zip.putNextEntry(new ZipEntry(base));
105 FileInputStream in = new FileInputStream(target);
106 try {
107 IOUtils.write(in, zip);
108 } finally {
109 in.close();
110 }
111 }
112 }
113
114 /**
115 * Zip the given source into dest.
116 *
117 * @param src
118 * the source {@link File} (which can be a directory)
119 * @param dest
120 * the destination <tt>.zip</tt> file
121 * @param srctIsRoot
122 * FALSE if we need to add a {@link ZipEntry} for src, TRUE to
123 * add it at the root of the ZIP
124 *
125 * @throws IOException
126 * in case of I/O error
127 */
128 public static void zip(File src, File dest, boolean srcIsRoot)
129 throws IOException {
130 OutputStream out = new FileOutputStream(dest);
131 try {
132 ZipOutputStream zip = new ZipOutputStream(out);
133 try {
134 IOUtils.zip(zip, "", src, srcIsRoot);
135 } finally {
136 zip.close();
137 }
138 } finally {
139 out.close();
140 }
141 }
142
143 /**
144 * Write the {@link String} content to {@link File}.
145 *
146 * @param dir
147 * the directory where to write the {@link File}
148 * @param filename
149 * the {@link File} name
150 * @param content
151 * the content
152 *
153 * @throws IOException
154 * in case of I/O error
155 */
156 public static void writeSmallFile(File dir, String filename, String content)
157 throws IOException {
158 if (!dir.exists()) {
159 dir.mkdirs();
160 }
161
162 FileWriter writerVersion = new FileWriter(new File(dir, filename));
163 try {
164 writerVersion.write(content);
165 } finally {
166 writerVersion.close();
167 }
168 }
169
170 /**
171 * Read the whole {@link File} content into a {@link String}.
172 *
173 * @param file
174 * the {@link File}
175 *
176 * @return the content
177 *
178 * @throws IOException
179 * in case of I/O error
180 */
181 public static String readSmallFile(File file) throws IOException {
182 BufferedReader reader = new BufferedReader(new FileReader(file));
183 try {
184 StringBuilder builder = new StringBuilder();
185 for (String line = reader.readLine(); line != null; line = reader
186 .readLine()) {
187 builder.append(line);
188 }
189 return builder.toString();
190 } finally {
191 reader.close();
192 }
193 }
194
195 /**
196 * Recursively delete the given {@link File}, which may of course also be a
197 * directory.
198 * <p>
199 * Will silently continue in case of error.
200 *
201 * @param target
202 * the target to delete
203 */
204 public static void deltree(File target) {
205 for (File file : target.listFiles()) {
206 if (file.isDirectory()) {
207 deltree(file);
208 } else {
209 if (!file.delete()) {
210 System.err.println("Cannot delete file: "
211 + file.getAbsolutePath());
212 }
213 }
214 }
215
216 if (!target.delete()) {
217 System.err.println("Cannot delete file: "
218 + target.getAbsolutePath());
219 }
220 }
b607df60
NR
221
222 /**
223 * Convert the given {@link InputStream} (which should allow calls to
224 * {@link InputStream#reset() for better perfs}) into an {@link Image}
225 * object, respecting the EXIF transformations if any.
226 *
227 * @param in
228 * the 'resetable' {@link InputStream}
229 *
230 * @return the {@link Image} object
231 *
232 * @throws IOException
233 * in case of IO error
234 */
16d59378 235 public static BufferedImage toImage(InputStream in) throws IOException {
b607df60
NR
236 MarkableFileInputStream tmpIn = null;
237 File tmp = null;
238 try {
239 in.reset();
240 } catch (IOException e) {
241 tmp = File.createTempFile("fanfic-tmp-image", ".tmp");
242 tmp.deleteOnExit();
243 IOUtils.write(in, tmp);
244 tmpIn = new MarkableFileInputStream(new FileInputStream(tmp));
245 }
246
247 int orientation;
248 try {
249 orientation = getExifTransorm(in);
250 } catch (Exception e) {
251 // no EXIF transform, ok
252 orientation = -1;
253 }
254
255 in.reset();
256 BufferedImage image = ImageIO.read(in);
257
258 if (image == null) {
259 if (tmp != null) {
260 tmp.delete();
261 tmpIn.close();
262 }
263 throw new IOException("Failed to convert input to image");
264 }
265
266 // Note: this code has been found on internet;
267 // thank you anonymous coder.
268 int width = image.getWidth();
269 int height = image.getHeight();
270 AffineTransform affineTransform = new AffineTransform();
271
272 switch (orientation) {
273 case 1:
274 affineTransform = null;
275 break;
276 case 2: // Flip X
277 affineTransform.scale(-1.0, 1.0);
278 affineTransform.translate(-width, 0);
279 break;
280 case 3: // PI rotation
281 affineTransform.translate(width, height);
282 affineTransform.rotate(Math.PI);
283 break;
284 case 4: // Flip Y
285 affineTransform.scale(1.0, -1.0);
286 affineTransform.translate(0, -height);
287 break;
288 case 5: // - PI/2 and Flip X
289 affineTransform.rotate(-Math.PI / 2);
290 affineTransform.scale(-1.0, 1.0);
291 break;
292 case 6: // -PI/2 and -width
293 affineTransform.translate(height, 0);
294 affineTransform.rotate(Math.PI / 2);
295 break;
296 case 7: // PI/2 and Flip
297 affineTransform.scale(-1.0, 1.0);
298 affineTransform.translate(-height, 0);
299 affineTransform.translate(0, width);
300 affineTransform.rotate(3 * Math.PI / 2);
301 break;
302 case 8: // PI / 2
303 affineTransform.translate(0, width);
304 affineTransform.rotate(3 * Math.PI / 2);
305 break;
306 default:
307 affineTransform = null;
308 break;
309 }
310
311 if (affineTransform != null) {
312 AffineTransformOp affineTransformOp = new AffineTransformOp(
313 affineTransform, AffineTransformOp.TYPE_BILINEAR);
314
315 BufferedImage transformedImage = new BufferedImage(width, height,
316 image.getType());
317 transformedImage = affineTransformOp
318 .filter(image, transformedImage);
319
320 image = transformedImage;
321 }
322 //
323
324 if (tmp != null) {
325 tmp.delete();
326 tmpIn.close();
327 }
328
329 return image;
330 }
331
16d59378
NR
332 /**
333 * Open the given /-separated resource (from the binary root).
334 *
335 * @param name
336 * the resource name
337 *
338 * @return the opened resource if found, NLL if not
339 */
340 public static InputStream openResource(String name) {
341 ClassLoader loader = IOUtils.class.getClassLoader();
342 if (loader == null) {
343 loader = ClassLoader.getSystemClassLoader();
344 }
345
346 return loader.getResourceAsStream(name);
347 }
348
b607df60
NR
349 /**
350 * Return the EXIF transformation flag of this image if any.
351 *
352 * <p>
353 * Note: this code has been found on internet; thank you anonymous coder.
354 * </p>
355 *
356 * @param in
357 * the data {@link InputStream}
358 *
359 * @return the transformation flag if any
360 *
361 * @throws IOException
362 * in case of IO error
363 */
16d59378 364 private static int getExifTransorm(InputStream in) throws IOException {
b607df60
NR
365 int[] exif_data = new int[100];
366 int set_flag = 0;
367 int is_motorola = 0;
368
369 /* Read File head, check for JPEG SOI + Exif APP1 */
370 for (int i = 0; i < 4; i++)
371 exif_data[i] = in.read();
372
373 if (exif_data[0] != 0xFF || exif_data[1] != 0xD8
374 || exif_data[2] != 0xFF || exif_data[3] != 0xE1)
375 return -2;
376
377 /* Get the marker parameter length count */
378 int length = (in.read() << 8 | in.read());
379
380 /* Length includes itself, so must be at least 2 */
381 /* Following Exif data length must be at least 6 */
382 if (length < 8)
383 return -1;
384 length -= 8;
385 /* Read Exif head, check for "Exif" */
386 for (int i = 0; i < 6; i++)
387 exif_data[i] = in.read();
388
389 if (exif_data[0] != 0x45 || exif_data[1] != 0x78
390 || exif_data[2] != 0x69 || exif_data[3] != 0x66
391 || exif_data[4] != 0 || exif_data[5] != 0)
392 return -1;
393
394 /* Read Exif body */
395 length = length > exif_data.length ? exif_data.length : length;
396 for (int i = 0; i < length; i++)
397 exif_data[i] = in.read();
398
399 if (length < 12)
400 return -1; /* Length of an IFD entry */
401
402 /* Discover byte order */
403 if (exif_data[0] == 0x49 && exif_data[1] == 0x49)
404 is_motorola = 0;
405 else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D)
406 is_motorola = 1;
407 else
408 return -1;
409
410 /* Check Tag Mark */
411 if (is_motorola == 1) {
412 if (exif_data[2] != 0)
413 return -1;
414 if (exif_data[3] != 0x2A)
415 return -1;
416 } else {
417 if (exif_data[3] != 0)
418 return -1;
419 if (exif_data[2] != 0x2A)
420 return -1;
421 }
422
423 /* Get first IFD offset (offset to IFD0) */
424 int offset;
425 if (is_motorola == 1) {
426 if (exif_data[4] != 0)
427 return -1;
428 if (exif_data[5] != 0)
429 return -1;
430 offset = exif_data[6];
431 offset <<= 8;
432 offset += exif_data[7];
433 } else {
434 if (exif_data[7] != 0)
435 return -1;
436 if (exif_data[6] != 0)
437 return -1;
438 offset = exif_data[5];
439 offset <<= 8;
440 offset += exif_data[4];
441 }
442 if (offset > length - 2)
443 return -1; /* check end of data segment */
444
445 /* Get the number of directory entries contained in this IFD */
446 int number_of_tags;
447 if (is_motorola == 1) {
448 number_of_tags = exif_data[offset];
449 number_of_tags <<= 8;
450 number_of_tags += exif_data[offset + 1];
451 } else {
452 number_of_tags = exif_data[offset + 1];
453 number_of_tags <<= 8;
454 number_of_tags += exif_data[offset];
455 }
456 if (number_of_tags == 0)
457 return -1;
458 offset += 2;
459
460 /* Search for Orientation Tag in IFD0 */
461 for (;;) {
462 if (offset > length - 12)
463 return -1; /* check end of data segment */
464 /* Get Tag number */
465 int tagnum;
466 if (is_motorola == 1) {
467 tagnum = exif_data[offset];
468 tagnum <<= 8;
469 tagnum += exif_data[offset + 1];
470 } else {
471 tagnum = exif_data[offset + 1];
472 tagnum <<= 8;
473 tagnum += exif_data[offset];
474 }
475 if (tagnum == 0x0112)
476 break; /* found Orientation Tag */
477 if (--number_of_tags == 0)
478 return -1;
479 offset += 12;
480 }
481
482 /* Get the Orientation value */
483 if (is_motorola == 1) {
484 if (exif_data[offset + 8] != 0)
485 return -1;
486 set_flag = exif_data[offset + 9];
487 } else {
488 if (exif_data[offset + 9] != 0)
489 return -1;
490 set_flag = exif_data[offset + 8];
491 }
492 if (set_flag > 8)
493 return -1;
494
495 return set_flag;
496 }
ec1f3444 497}