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 visitFile(
077 Path file,
078 BasicFileAttributes basicFileAttributes)
079 throws IOException {
080
081 Files.delete(file);
082
083 return FileVisitResult.CONTINUE;
084 }
085
086 @Override
087 public FileVisitResult visitFileFailed(
088 Path file, IOException ioe)
089 throws IOException {
090
091 if (quiet || (ioe instanceof NoSuchFileException)) {
092 return FileVisitResult.CONTINUE;
093 }
094
095 throw ioe;
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 preVisitDirectory(
149 Path dir, BasicFileAttributes basicFileAttributes)
150 throws IOException {
151
152 Files.copy(
153 dir, toPath.resolve(fromPath.relativize(dir)),
154 StandardCopyOption.COPY_ATTRIBUTES,
155 StandardCopyOption.REPLACE_EXISTING);
156
157 fileTimes.put(dir, Files.getLastModifiedTime(dir));
158
159 return FileVisitResult.CONTINUE;
160 }
161
162 @Override
163 public FileVisitResult visitFile(
164 Path file, BasicFileAttributes basicFileAttributes)
165 throws IOException {
166
167 Path toFile = toPath.resolve(fromPath.relativize(file));
168
169 if (atomicMove.get()) {
170 try {
171 Files.move(
172 file, toFile,
173 StandardCopyOption.ATOMIC_MOVE,
174 StandardCopyOption.REPLACE_EXISTING);
175
176 touched.set(true);
177
178 return FileVisitResult.CONTINUE;
179 }
180 catch (AtomicMoveNotSupportedException amnse) {
181 atomicMove.set(false);
182 }
183 }
184
185 Files.copy(
186 file, toFile, StandardCopyOption.COPY_ATTRIBUTES,
187 StandardCopyOption.REPLACE_EXISTING);
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() + " is " +
290 size + " bytes but should " + length + " bytes");
291 }
292 }
293 }
294
295 return rawSize.get();
296 }
297
298 public static Path zip(
299 Path sourcePath, Path destDirPath,
300 CompressionLevel compressionLevel)
301 throws IOException {
302
303 Path zipPath = Files.createTempFile(destDirPath, null, null);
304
305 try (OutputStream outputStream = Files.newOutputStream(zipPath)) {
306 ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
307
308 zipOutputStream.setLevel(compressionLevel.getLevel());
309
310 long startTime = System.currentTimeMillis();
311
312 long rawSize = zip(sourcePath, zipOutputStream);
313
314 if (_log.isDebugEnabled()) {
315 long zippedSize = Files.size(zipPath);
316
317 long time = (System.currentTimeMillis() - startTime) / 1000;
318
319 double compressionRatio = rawSize / zippedSize;
320
321 StringBundler sb = new StringBundler(13);
322
323 sb.append("Zipped ");
324 sb.append(sourcePath);
325 sb.append(" (");
326 sb.append(rawSize);
327 sb.append(" bytes) to ");
328 sb.append(zipPath);
329 sb.append(" (");
330 sb.append(zippedSize);
331 sb.append(" bytes)\" in ");
332 sb.append(time);
333 sb.append("s with a ");
334 sb.append(compressionRatio);
335 sb.append("compression ratio");
336
337 _log.debug(sb.toString());
338 }
339 }
340 catch (IOException ioe) {
341 Files.delete(zipPath);
342
343 throw ioe;
344 }
345
346 return zipPath;
347 }
348
349 public static long zip(
350 final Path sourcePath, ZipOutputStream zipOutputStream)
351 throws IOException {
352
353 final AtomicLong rawSize = new AtomicLong();
354
355 final byte[] buffer = new byte[16];
356
357 try (ZipOutputStream autoClosezipOutputStream = zipOutputStream) {
358 Files.walkFileTree(
359 sourcePath,
360 new SimpleFileVisitor<Path>() {
361
362 @Override
363 public FileVisitResult visitFile(
364 Path file, BasicFileAttributes basicFileAttributes)
365 throws IOException {
366
367 Path relativePath = sourcePath.relativize(file);
368
369 ZipEntry zipEntry = new ZipEntry(
370 relativePath.toString());
371
372 FileTime fileTime =
373 basicFileAttributes.lastModifiedTime();
374
375 rawSize.addAndGet(basicFileAttributes.size());
376
377 BigEndianCodec.putLong(buffer, 0, fileTime.toMillis());
378 BigEndianCodec.putLong(
379 buffer, 8, basicFileAttributes.size());
380
381 zipEntry.setExtra(buffer);
382
383 autoClosezipOutputStream.putNextEntry(zipEntry);
384
385 Files.copy(file, autoClosezipOutputStream);
386
387 autoClosezipOutputStream.closeEntry();
388
389 return FileVisitResult.CONTINUE;
390 }
391
392 });
393 }
394
395 return rawSize.get();
396 }
397
398 private static final Log _log = LogFactoryUtil.getLog(FileHelperUtil.class);
399
400 }