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 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    }