| IOUtil.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 or later
5 * as published by the Free Software Foundation. This program is distributed
6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * © CrossWire Bible Society, 2005 - 2016
18 *
19 */
20 package org.crosswire.common.util;
21
22 import java.io.Closeable;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.net.MalformedURLException;
28 import java.net.URI;
29 import java.util.Enumeration;
30
31 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
32 import org.apache.commons.compress.archivers.zip.ZipFile;
33 import org.crosswire.jsword.JSMsg;
34 import org.slf4j.LoggerFactory;
35
36 /**
37 * .
38 *
39 * @see gnu.lgpl.License The GNU Lesser General Public License for details.
40 * @author Joe Walker
41 */
42 public final class IOUtil {
43 /**
44 * Prevent instantiation
45 */
46 private IOUtil() {
47 }
48
49 /**
50 * Unpack a zip file to a given directory. Honor the paths as given in the
51 * zip file.
52 *
53 * @param file
54 * The zip file to download
55 * @param destdir
56 * The directory to unpack up
57 * @throws IOException
58 * If there is an file error
59 */
60 public static void unpackZip(File file, File destdir) throws IOException {
61 unpackZip(file, destdir, true);
62 }
63
64 /**
65 * Unpack a zip file to a given directory. Honor the paths as given in the
66 * zip file.
67 *
68 * @param file
69 * The zip file to download
70 * @param destdir
71 * The directory to unpack up
72 * @param include
73 * true to indicate the next arguments will be a filter that only includes what is specified.
74 * @param includeExcludes
75 * a list of case insensitive patterns that will act as an inclusion or exclusion prefix
76 * @throws IOException
77 * If there is an file error
78 */
79 public static void unpackZip(File file, File destdir, boolean include, String... includeExcludes) throws IOException {
80 // unpack the zip.
81 byte[] dbuf = new byte[4096];
82 ZipFile zf = null;
83 try {
84 zf = new ZipFile(file);
85 Enumeration<ZipArchiveEntry> entries = zf.getEntries();
86 while (entries.hasMoreElements()) {
87 ZipArchiveEntry entry = entries.nextElement();
88 String entrypath = entry.getName();
89
90 //check filters
91 if (includeExcludes != null && includeExcludes.length > 0) {
92 //if include, then we attempt to match ANY path
93 //if exclude, then we ensure that we match NO path
94 boolean skip = include;
95 for (String filter : includeExcludes) {
96 final boolean matchesPath = entrypath.toLowerCase().startsWith(filter);
97 //for includes, ANY match counts, so we override the default of true to false to say 'not skip'
98 if (include && matchesPath) {
99 skip = false;
100 }
101
102 //for excludes, the default of skip is false
103 if (!include && matchesPath) {
104 skip = true;
105 }
106 }
107
108 if (skip) {
109 continue;
110 }
111 }
112
113 File entryFile = new File(destdir, entrypath);
114 File parentDir = entryFile.getParentFile();
115 // Is it already a directory ?
116 if (!parentDir.isDirectory()) {
117 // Create the directory and make sure it worked.
118 if (!parentDir.mkdirs()) {
119 // TRANSLATOR: Error condition: A directory could not be created. {0} is a placeholder for the directory
120 throw new MalformedURLException(JSMsg.gettext("The URL {0} could not be created as a directory.", parentDir.toString()));
121 }
122 }
123
124 // write entryFile from zip to filesystem but avoid writing dir entries out as files
125 if (!entry.isDirectory()) {
126
127 URI child = NetUtil.getURI(entryFile);
128
129 OutputStream dataOut = NetUtil.getOutputStream(child);
130 InputStream dataIn = zf.getInputStream(entry);
131
132 while (true) {
133 int count = dataIn.read(dbuf);
134 if (count == -1) {
135 break;
136 }
137 dataOut.write(dbuf, 0, count);
138 }
139
140 dataOut.close();
141 }
142 }
143 } finally {
144 IOUtil.close(zf);
145 }
146 }
147
148 /**
149 * Get a zip entry by specification, returning a buffer of the contents.
150 * If there is an error, return a zero length buffer.
151 *
152 * @param entrySpec This is of the form /path/to/zip!entryName
153 * @return the contents as a buffer
154 * @throws IOException
155 */
156 public static byte[] getZipEntry(String entrySpec) throws IOException {
157 // Get the buffer
158 byte[] buffer = new byte[0];
159 String[] parts = StringUtil.split(entrySpec, '!');
160 ZipFile zipFile = null;
161 InputStream zin = null;
162 try {
163 zipFile = new ZipFile(parts[0]);
164 ZipArchiveEntry entry = zipFile.getEntry(parts[1]);
165 zin = zipFile.getInputStream(entry);
166 int size = (int) entry.getSize();
167 buffer = new byte[size];
168 // Repeatedly read until all has been read
169 int offset = 0;
170 while (offset < size) {
171 offset += zin.read(buffer, offset, size - offset);
172 }
173
174 if (offset != size) {
175 log.error("Error: Could not read {} bytes, instead {}, for {} from {}", Integer.toString(size), Integer.toString(offset), parts[1], parts[0]);
176 }
177 } finally {
178 IOUtil.close(zin);
179 IOUtil.close(zipFile);
180 }
181 return buffer;
182 }
183
184 /**
185 * Closes any {@link Closeable} object
186 *
187 * @param closeable
188 * The zip file to close
189 */
190 public static void close(Closeable closeable) {
191 if (null != closeable) {
192 try {
193 closeable.close();
194 } catch (IOException ex) {
195 log.error("close", ex);
196 }
197 }
198 }
199
200 /**
201 * The log stream
202 */
203 private static final org.slf4j.Logger log = LoggerFactory.getLogger(IOUtil.class);
204 }
205