/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.sync.engine.util;
import com.liferay.sync.engine.document.library.util.FileEventUtil;
import com.liferay.sync.engine.model.SyncAccount;
import com.liferay.sync.engine.model.SyncFile;
import com.liferay.sync.engine.service.SyncAccountService;
import com.liferay.sync.engine.service.SyncFileService;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Shinn Lok
*/
public class FileUtil {
public static boolean checkFilePath(Path filePath) {
// Check to see if the file or folder is still being written to. If
// it is, wait until the process is finished before making any future
// modifications. This is used to prevent file system interruptions.
try {
if (_checkInProgressFilePathNames.contains(filePath.toString())) {
return false;
}
_checkInProgressFilePathNames.add(filePath.toString());
while (true) {
long size1 = FileUtils.sizeOf(filePath.toFile());
if (size1 == 0) {
Thread.sleep(50);
}
else {
Thread.sleep(size1 / 1048576);
}
long size2 = FileUtils.sizeOf(filePath.toFile());
if (size1 == size2) {
_checkInProgressFilePathNames.remove(filePath.toString());
return true;
}
}
}
catch (Exception e) {
return true;
}
}
public static boolean checksumsEqual(String checksum1, String checksum2) {
if (Validator.isBlank(checksum1) || Validator.isBlank(checksum2)) {
return false;
}
return checksum1.equals(checksum2);
}
public static void deleteFile(Path filePath) {
try {
deleteFile(filePath, true);
}
catch (Exception e) {
}
}
public static void deleteFile(final Path filePath, boolean retry)
throws IOException {
if ((filePath == null) || notExists(filePath)) {
return;
}
try {
Files.deleteIfExists(filePath);
}
catch (Exception e) {
if (!retry) {
throw e;
}
PathCallable pathCallable = new PathCallable(filePath) {
@Override
public Object call() throws Exception {
FileTime fileTime = Files.getLastModifiedTime(filePath);
if (fileTime.toMillis() <= getStartTime()) {
Files.deleteIfExists(filePath);
}
return null;
}
};
FileLockRetryUtil.registerPathCallable(pathCallable);
}
}
public static boolean exists(Path filePath) {
return Files.exists(filePath, LinkOption.NOFOLLOW_LINKS);
}
public static void fireDeleteEvents(Path filePath) throws IOException {
long startTime = System.currentTimeMillis();
Files.walkFileTree(
filePath,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(
Path filePath, BasicFileAttributes basicFileAttributes) {
SyncFile syncFile = SyncFileService.fetchSyncFile(
filePath.toString());
if (syncFile == null) {
syncFile = SyncFileService.fetchSyncFile(
FileKeyUtil.getFileKey(filePath));
}
if (syncFile != null) {
syncFile.setLocalSyncTime(System.currentTimeMillis());
SyncFileService.update(syncFile);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(
Path filePath, BasicFileAttributes basicFileAttributes) {
SyncFile syncFile = SyncFileService.fetchSyncFile(
filePath.toString());
if (syncFile == null) {
syncFile = SyncFileService.fetchSyncFile(
FileKeyUtil.getFileKey(filePath));
}
if (syncFile != null) {
syncFile.setLocalSyncTime(System.currentTimeMillis());
SyncFileService.update(syncFile);
}
return FileVisitResult.CONTINUE;
}
});
List<SyncFile> deletedSyncFiles = SyncFileService.findSyncFiles(
filePath.toString(), startTime);
for (SyncFile deletedSyncFile : deletedSyncFiles) {
if (!notExists(Paths.get(deletedSyncFile.getFilePathName()))) {
continue;
}
if (deletedSyncFile.getTypePK() == 0) {
SyncFileService.deleteSyncFile(deletedSyncFile, false);
continue;
}
if (deletedSyncFile.isFolder()) {
FileEventUtil.deleteFolder(
deletedSyncFile.getSyncAccountId(), deletedSyncFile);
}
else {
FileEventUtil.deleteFile(
deletedSyncFile.getSyncAccountId(), deletedSyncFile);
}
}
}
public static String getChecksum(Path filePath) {
InputStream fileInputStream = null;
try {
if (!isValidChecksum(filePath)) {
return "";
}
fileInputStream = Files.newInputStream(filePath);
byte[] bytes = DigestUtils.sha1(fileInputStream);
return Base64.encodeBase64String(bytes);
}
catch (IOException ioe) {
if (_logger.isDebugEnabled()) {
_logger.debug(ioe.getMessage(), ioe);
}
return "";
}
finally {
StreamUtil.cleanUp(fileInputStream);
}
}
public static FileLock getFileLock(FileChannel fileChannel) {
try {
return fileChannel.tryLock();
}
catch (Exception e) {
_logger.error(e.getMessage(), e);
return null;
}
}
public static Path getFilePath(String first, String... more) {
FileSystem fileSystem = FileSystems.getDefault();
return fileSystem.getPath(first, more);
}
public static String getFilePathName(String first, String... more) {
Path filePath = getFilePath(first, more);
return filePath.toString();
}
public static long getLastModifiedTime(Path filePath) throws IOException {
if (!exists(filePath)) {
return 0;
}
FileTime fileTime = Files.getLastModifiedTime(
filePath, LinkOption.NOFOLLOW_LINKS);
return fileTime.toMillis();
}
public static String getNextFilePathName(String filePathName) {
Path filePath = Paths.get(filePathName);
Path parentFilePath = filePath.getParent();
for (int i = 0;; i++) {
StringBuilder sb = new StringBuilder();
sb.append(FilenameUtils.getBaseName(filePathName));
if (i > 0) {
sb.append(" (");
sb.append(i);
sb.append(")");
}
String extension = FilenameUtils.getExtension(filePathName);
if (extension.length() > 0) {
sb.append(".");
sb.append(extension);
}
String tempFilePathName = getFilePathName(
parentFilePath.toString(), sb.toString());
if (SyncFileService.fetchSyncFile(tempFilePathName) == null) {
Path tempFilePath = Paths.get(tempFilePathName);
if (!exists(tempFilePath)) {
return tempFilePathName;
}
}
}
}
public static String getSanitizedFileName(
String fileName, String extension) {
fileName = fileName.trim();
for (String blacklistChar : PropsValues.SYNC_FILE_BLACKLIST_CHARS) {
blacklistChar = unescapeJava(blacklistChar);
fileName = fileName.replace(blacklistChar, "_");
}
for (String blacklistCharLast :
PropsValues.SYNC_FILE_BLACKLIST_CHARS_LAST) {
blacklistCharLast = unescapeJava(blacklistCharLast);
if (fileName.endsWith(blacklistCharLast)) {
fileName = fileName.substring(0, fileName.length() - 1);
}
}
if (!Validator.isBlank(extension)) {
extension = extension.trim();
int x = fileName.lastIndexOf(".");
if ((x == -1) ||
!extension.equalsIgnoreCase(fileName.substring(x + 1))) {
fileName += "." + extension;
}
}
if (fileName.length() > 255) {
int x = fileName.length() - 1;
if (!Validator.isBlank(extension)) {
x = fileName.lastIndexOf(".");
}
int y = x - (fileName.length() - 255);
fileName = fileName.substring(0, y) + fileName.substring(x);
}
return fileName;
}
public static Path getTempFilePath(SyncFile syncFile) {
SyncAccount syncAccount = SyncAccountService.fetchSyncAccount(
syncFile.getSyncAccountId());
if (syncAccount == null) {
return null;
}
return getFilePath(
syncAccount.getFilePathName(), ".data",
String.valueOf(syncFile.getSyncFileId()));
}
public static boolean isHidden(Path filePath) {
if (!PropsValues.SYNC_FILE_IGNORE_HIDDEN || !exists(filePath)) {
return false;
}
try {
return Files.isHidden(filePath);
}
catch (IOException ioe) {
return false;
}
}
public static boolean isIgnoredFileName(String fileName) {
return _syncFileIgnoreNames.contains(
StringEscapeUtils.escapeJava(fileName));
}
public static boolean isIgnoredFilePath(Path filePath) {
String fileName = String.valueOf(filePath.getFileName());
if (isIgnoredFileName(fileName) || isTempFile(filePath) ||
isHidden(filePath) || isShortcut(filePath)) {
return true;
}
SyncFile syncFile = SyncFileService.fetchSyncFile(filePath.toString());
if (syncFile == null) {
return isIgnoredFilePath(filePath.getParent());
}
return false;
}
public static boolean isModified(SyncFile syncFile) {
if (syncFile.getFilePathName() == null) {
return true;
}
Path filePath = Paths.get(syncFile.getFilePathName());
return isModified(syncFile, filePath);
}
public static boolean isModified(SyncFile syncFile, Path filePath) {
if ((filePath == null) || notExists(filePath)) {
return true;
}
try {
if (MSOfficeFileUtil.isLegacyExcelFile(filePath)) {
Date lastSavedDate = MSOfficeFileUtil.getLastSavedDate(
filePath);
if ((lastSavedDate != null) && (lastSavedDate.getTime() ==
GetterUtil.getLong(
syncFile.getLocalExtraSettingValue(
"lastSavedDate")))) {
return false;
}
}
if (syncFile.getSize() > 0) {
long lastModifiedTime = getLastModifiedTime(filePath);
long modifiedTime = syncFile.getModifiedTime();
if (OSDetector.isUnix()) {
modifiedTime = modifiedTime / 1000 * 1000;
}
if (((lastModifiedTime == modifiedTime) ||
(lastModifiedTime ==
syncFile.getPreviousModifiedTime())) &&
FileKeyUtil.hasFileKey(
filePath, syncFile.getSyncFileId())) {
return false;
}
}
}
catch (IOException ioe) {
if (_logger.isDebugEnabled()) {
_logger.debug(ioe.getMessage(), ioe);
}
}
try {
if ((syncFile.getSize() > 0) &&
(syncFile.getSize() != Files.size(filePath))) {
return true;
}
}
catch (IOException ioe) {
if (_logger.isDebugEnabled()) {
_logger.debug(ioe.getMessage(), ioe);
}
}
if (Validator.isBlank(syncFile.getChecksum())) {
return true;
}
return !checksumsEqual(getChecksum(filePath), syncFile.getChecksum());
}
public static boolean isRealFilePath(Path filePath) {
try {
Path realFilePath = filePath.toRealPath(LinkOption.NOFOLLOW_LINKS);
String realFilePathString = realFilePath.toString();
return realFilePathString.equals(filePath.toString());
}
catch (Exception e) {
return false;
}
}
public static boolean isShortcut(Path filePath) {
if (Files.isSymbolicLink(filePath)) {
return true;
}
String fileName = String.valueOf(filePath.getFileName());
if (fileName.endsWith(".lnk")) {
return true;
}
return false;
}
public static boolean isTempFile(Path filePath) {
if (MSOfficeFileUtil.isTempCreatedFile(filePath)) {
return true;
}
return false;
}
public static boolean isUnsynced(Path filePath) {
SyncFile syncFile = SyncFileService.fetchSyncFile(filePath.toString());
if (syncFile == null) {
return isUnsynced(filePath.getParent());
}
return syncFile.isUnsynced();
}
public static boolean isValidChecksum(Path filePath) throws IOException {
if (notExists(filePath) ||
(Files.size(filePath) >
PropsValues.SYNC_FILE_CHECKSUM_THRESHOLD_SIZE)) {
return false;
}
return true;
}
public static boolean isValidFileName(String fileName) {
if (StringUtils.isBlank(fileName)) {
return false;
}
for (String blacklistChar : PropsValues.SYNC_FILE_BLACKLIST_CHARS) {
blacklistChar = unescapeJava(blacklistChar);
if (fileName.contains(blacklistChar)) {
return false;
}
}
for (String blacklistLastChar :
PropsValues.SYNC_FILE_BLACKLIST_CHARS_LAST) {
blacklistLastChar = unescapeJava(blacklistLastChar);
if (fileName.endsWith(blacklistLastChar)) {
return false;
}
}
String nameWithoutExtension = FilenameUtils.removeExtension(fileName);
for (String blacklistName : PropsValues.SYNC_FILE_BLACKLIST_NAMES) {
if (nameWithoutExtension.equalsIgnoreCase(blacklistName)) {
return false;
}
}
return true;
}
public static void moveFile(Path sourceFilePath, Path targetFilePath) {
try {
if (Files.isDirectory(sourceFilePath)) {
moveFolder(sourceFilePath, targetFilePath);
return;
}
moveFile(sourceFilePath, targetFilePath, true);
}
catch (Exception e) {
_logger.error(e.getMessage(), e);
}
}
public static void moveFile(
final Path sourceFilePath, final Path targetFilePath, boolean retry)
throws IOException {
try {
Files.move(
sourceFilePath, targetFilePath, StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException ioe) {
if (!retry) {
throw ioe;
}
PathCallable pathCallable = new PathCallable(sourceFilePath) {
@Override
public Object call() throws Exception {
FileTime fileTime = Files.getLastModifiedTime(
targetFilePath);
if (fileTime.toMillis() <= getStartTime()) {
Files.move(
sourceFilePath, targetFilePath,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
else {
Files.deleteIfExists(sourceFilePath);
}
return null;
}
};
FileLockRetryUtil.registerPathCallable(pathCallable);
}
}
public static void moveFolder(
final Path sourceParentFilePath, final Path targetParentFilePath)
throws IOException {
try {
Files.move(
sourceParentFilePath, targetParentFilePath,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
catch (Exception e) {
Files.walkFileTree(
sourceParentFilePath,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(
Path filePath,
BasicFileAttributes basicFileAttributes) {
return moveFilePath(filePath);
}
@Override
public FileVisitResult visitFile(
Path filePath,
BasicFileAttributes basicFileAttributes) {
return moveFilePath(filePath);
}
@Override
public FileVisitResult visitFileFailed(
Path filePath, IOException ioe) {
return FileVisitResult.CONTINUE;
}
protected FileVisitResult moveFilePath(Path filePath) {
Path targetFilePath = targetParentFilePath.resolve(
sourceParentFilePath.relativize(filePath));
try {
Files.move(
filePath, targetFilePath,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
catch (Exception e1) {
try {
Files.copy(
filePath, targetFilePath,
StandardCopyOption.COPY_ATTRIBUTES);
}
catch (Exception e2) {
_logger.error(e2.getMessage(), e2);
}
}
return FileVisitResult.CONTINUE;
}
});
}
}
public static boolean notExists(Path filePath) {
return Files.notExists(filePath, LinkOption.NOFOLLOW_LINKS);
}
public static void releaseFileLock(FileLock fileLock) {
try {
if (fileLock != null) {
fileLock.release();
}
}
catch (Exception e) {
if (_logger.isDebugEnabled()) {
_logger.debug(e.getMessage(), e);
}
}
}
public static void setModifiedTime(Path filePath, long modifiedTime)
throws IOException {
if (!exists(filePath)) {
return;
}
FileTime fileTime = FileTime.fromMillis(modifiedTime);
Files.setLastModifiedTime(filePath, fileTime);
}
public static String unescapeJava(String value) {
if (value.startsWith("\\u")) {
return StringEscapeUtils.unescapeJava(value);
}
return value;
}
private static final Logger _logger = LoggerFactory.getLogger(
FileUtil.class);
private static final List<String> _checkInProgressFilePathNames =
new CopyOnWriteArrayList<>();
private static final Set<String> _syncFileIgnoreNames = new HashSet<>(
Arrays.asList(PropsValues.SYNC_FILE_IGNORE_NAMES));
}