001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
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    /**
047     * @author Shuyang Zhou
048     */
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    }