001
014
015 package com.liferay.portal.fabric.netty.fileserver;
016
017 import com.liferay.portal.kernel.io.BigEndianCodec;
018 import com.liferay.portal.kernel.log.Log;
019 import com.liferay.portal.kernel.log.LogFactoryUtil;
020 import com.liferay.portal.kernel.util.ReflectionUtil;
021 import com.liferay.portal.kernel.util.StringBundler;
022
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.io.OutputStream;
026
027 import java.nio.file.AtomicMoveNotSupportedException;
028 import java.nio.file.FileVisitResult;
029 import java.nio.file.Files;
030 import java.nio.file.NoSuchFileException;
031 import java.nio.file.Path;
032 import java.nio.file.Paths;
033 import java.nio.file.SimpleFileVisitor;
034 import java.nio.file.StandardCopyOption;
035 import java.nio.file.attribute.BasicFileAttributes;
036 import java.nio.file.attribute.FileTime;
037
038 import java.util.HashMap;
039 import java.util.Map;
040 import java.util.concurrent.atomic.AtomicBoolean;
041 import java.util.concurrent.atomic.AtomicLong;
042 import java.util.zip.ZipEntry;
043 import java.util.zip.ZipInputStream;
044 import java.util.zip.ZipOutputStream;
045
046
049 public class FileHelperUtil {
050
051 public static final Path TEMP_DIR_PATH = Paths.get(
052 System.getProperty("java.io.tmpdir"));
053
054 public static void delete(final boolean quiet, Path... paths) {
055 try {
056 for (Path path : paths) {
057 Files.walkFileTree(
058 path,
059 new SimpleFileVisitor<Path>() {
060
061 @Override
062 public FileVisitResult postVisitDirectory(
063 Path dir, IOException ioe)
064 throws IOException {
065
066 if ((ioe != null) && !quiet) {
067 throw ioe;
068 }
069
070 Files.delete(dir);
071
072 return FileVisitResult.CONTINUE;
073 }
074
075 @Override
076 public FileVisitResult visitFileFailed(
077 Path file, IOException ioe)
078 throws IOException {
079
080 if (quiet || (ioe instanceof NoSuchFileException)) {
081 return FileVisitResult.CONTINUE;
082 }
083
084 throw ioe;
085 }
086
087 @Override
088 public FileVisitResult visitFile(
089 Path file,
090 BasicFileAttributes basicFileAttributes)
091 throws IOException {
092
093 Files.delete(file);
094
095 return FileVisitResult.CONTINUE;
096 }
097
098 });
099 }
100 }
101 catch (IOException ioe) {
102 if (!quiet) {
103 ReflectionUtil.throwException(ioe);
104 }
105 }
106 }
107
108 public static void delete(Path... paths) {
109 delete(false, paths);
110 }
111
112 public static void move(Path fromPath, final Path toPath)
113 throws IOException {
114
115 move(fromPath, toPath, true);
116 }
117
118 public static void move(
119 final Path fromPath, final Path toPath, boolean tryAtomicMove)
120 throws IOException {
121
122 final AtomicBoolean atomicMove = new AtomicBoolean(tryAtomicMove);
123 final AtomicBoolean touched = new AtomicBoolean();
124 final Map<Path, FileTime> fileTimes = new HashMap<>();
125
126 try {
127 Files.walkFileTree(
128 fromPath,
129 new SimpleFileVisitor<Path>() {
130
131 @Override
132 public FileVisitResult postVisitDirectory(
133 Path dir, IOException ioe)
134 throws IOException {
135
136 Files.setLastModifiedTime(
137 toPath.resolve(fromPath.relativize(dir)),
138 fileTimes.remove(dir));
139
140 if (atomicMove.get()) {
141 Files.delete(dir);
142 }
143
144 return FileVisitResult.CONTINUE;
145 }
146
147 @Override
148 public FileVisitResult visitFile(
149 Path file, BasicFileAttributes basicFileAttributes)
150 throws IOException {
151
152 Path toFile = toPath.resolve(fromPath.relativize(file));
153
154 if (atomicMove.get()) {
155 try {
156 Files.move(
157 file, toFile,
158 StandardCopyOption.ATOMIC_MOVE,
159 StandardCopyOption.REPLACE_EXISTING);
160
161 touched.set(true);
162
163 return FileVisitResult.CONTINUE;
164 }
165 catch (AtomicMoveNotSupportedException amnse) {
166 atomicMove.set(false);
167 }
168 }
169
170 Files.copy(
171 file, toFile, StandardCopyOption.COPY_ATTRIBUTES,
172 StandardCopyOption.REPLACE_EXISTING);
173
174 return FileVisitResult.CONTINUE;
175 }
176
177 @Override
178 public FileVisitResult preVisitDirectory(
179 Path dir, BasicFileAttributes basicFileAttributes)
180 throws IOException {
181
182 Files.copy(
183 dir, toPath.resolve(fromPath.relativize(dir)),
184 StandardCopyOption.COPY_ATTRIBUTES,
185 StandardCopyOption.REPLACE_EXISTING);
186
187 fileTimes.put(dir, Files.getLastModifiedTime(dir));
188
189 return FileVisitResult.CONTINUE;
190 }
191
192 });
193 }
194 catch (IOException ioe) {
195 delete(true, toPath);
196
197 if (touched.get()) {
198 throw new IOException(
199 "Source path " + fromPath + " was left in an " +
200 "inconsistent state",
201 ioe);
202 }
203
204 throw ioe;
205 }
206
207 if (!atomicMove.get()) {
208 delete(false, fromPath);
209 }
210 }
211
212 public static Path unzip(Path sourcePath, Path destDirPath)
213 throws IOException {
214
215 Path destPath = Files.createTempDirectory(destDirPath, null);
216
217 try (InputStream inputStream = Files.newInputStream(sourcePath)) {
218 long startTime = System.currentTimeMillis();
219
220 long rawSize = unzip(new ZipInputStream(inputStream), destPath);
221
222 if (_log.isDebugEnabled()) {
223 long zippedSize = Files.size(sourcePath);
224
225 long time = (System.currentTimeMillis() - startTime) / 1000;
226
227 double compressionRatio = rawSize / zippedSize;
228
229 StringBundler sb = new StringBundler(13);
230
231 sb.append("Unzipped ");
232 sb.append(sourcePath);
233 sb.append(" (");
234 sb.append(zippedSize);
235 sb.append(" bytes) to ");
236 sb.append(destPath);
237 sb.append(" (");
238 sb.append(rawSize);
239 sb.append(" bytes)\" in ");
240 sb.append(time);
241 sb.append("s with a ");
242 sb.append(compressionRatio);
243 sb.append("compression ratio");
244
245 _log.debug(sb.toString());
246 }
247 }
248 catch (IOException ioe) {
249 delete(destPath);
250
251 throw ioe;
252 }
253
254 return destPath;
255 }
256
257 public static long unzip(ZipInputStream zipInputStream, Path destPath)
258 throws IOException {
259
260 final AtomicLong rawSize = new AtomicLong();
261
262 try (ZipInputStream autoCloseZipInputStream = zipInputStream) {
263 ZipEntry zipEntry = null;
264
265 while ((zipEntry = autoCloseZipInputStream.getNextEntry()) !=
266 null) {
267
268 if (zipEntry.isDirectory()) {
269 continue;
270 }
271
272 Path entryPath = destPath.resolve(zipEntry.getName());
273
274 Files.createDirectories(entryPath.getParent());
275
276 long size = Files.copy(autoCloseZipInputStream, entryPath);
277
278 rawSize.addAndGet(size);
279
280 Files.setLastModifiedTime(
281 entryPath,
282 FileTime.fromMillis(
283 BigEndianCodec.getLong(zipEntry.getExtra(), 0)));
284
285 long length = BigEndianCodec.getLong(zipEntry.getExtra(), 8);
286
287 if (size != length) {
288 throw new IOException(
289 "Zip stream for entry " + zipEntry.getName() +
290 " is " + size + " bytes but should " + length +
291 " bytes");
292 }
293 }
294 }
295
296 return rawSize.get();
297 }
298
299 public static Path zip(
300 Path sourcePath, Path destDirPath,
301 CompressionLevel compressionLevel)
302 throws IOException {
303
304 Path zipPath = Files.createTempFile(destDirPath, null, null);
305
306 try (OutputStream outputStream = Files.newOutputStream(zipPath)) {
307 ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
308
309 zipOutputStream.setLevel(compressionLevel.getLevel());
310
311 long startTime = System.currentTimeMillis();
312
313 long rawSize = zip(sourcePath, zipOutputStream);
314
315 if (_log.isDebugEnabled()) {
316 long zippedSize = Files.size(zipPath);
317
318 long time = (System.currentTimeMillis() - startTime) / 1000;
319
320 double compressionRatio = rawSize / zippedSize;
321
322 StringBundler sb = new StringBundler(13);
323
324 sb.append("Zipped ");
325 sb.append(sourcePath);
326 sb.append(" (");
327 sb.append(rawSize);
328 sb.append(" bytes) to ");
329 sb.append(zipPath);
330 sb.append(" (");
331 sb.append(zippedSize);
332 sb.append(" bytes)\" in ");
333 sb.append(time);
334 sb.append("s with a ");
335 sb.append(compressionRatio);
336 sb.append("compression ratio");
337
338 _log.debug(sb.toString());
339 }
340 }
341 catch (IOException ioe) {
342 Files.delete(zipPath);
343
344 throw ioe;
345 }
346
347 return zipPath;
348 }
349
350 public static long zip(
351 final Path sourcePath, ZipOutputStream zipOutputStream)
352 throws IOException {
353
354 final AtomicLong rawSize = new AtomicLong();
355
356 final byte[] buffer = new byte[16];
357
358 try (ZipOutputStream autoClosezipOutputStream = zipOutputStream) {
359 Files.walkFileTree(
360 sourcePath,
361 new SimpleFileVisitor<Path>() {
362
363 @Override
364 public FileVisitResult visitFile(
365 Path file, BasicFileAttributes basicFileAttributes)
366 throws IOException {
367
368 Path relativePath = sourcePath.relativize(file);
369
370 ZipEntry zipEntry = new ZipEntry(
371 relativePath.toString());
372
373 FileTime fileTime =
374 basicFileAttributes.lastModifiedTime();
375
376 rawSize.addAndGet(basicFileAttributes.size());
377
378 BigEndianCodec.putLong(buffer, 0, fileTime.toMillis());
379 BigEndianCodec.putLong(
380 buffer, 8, basicFileAttributes.size());
381
382 zipEntry.setExtra(buffer);
383
384 autoClosezipOutputStream.putNextEntry(zipEntry);
385
386 Files.copy(file, autoClosezipOutputStream);
387
388 autoClosezipOutputStream.closeEntry();
389
390 return FileVisitResult.CONTINUE;
391 }
392
393 });
394 }
395
396 return rawSize.get();
397 }
398
399 private static final Log _log = LogFactoryUtil.getLog(FileHelperUtil.class);
400
401 }