/** * 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.file.system; import com.liferay.sync.engine.file.system.util.WatcherManager; import com.liferay.sync.engine.model.SyncAccount; import com.liferay.sync.engine.model.SyncFile; import com.liferay.sync.engine.model.SyncSite; import com.liferay.sync.engine.model.SyncWatchEvent; import com.liferay.sync.engine.service.SyncAccountService; import com.liferay.sync.engine.service.SyncFileService; import com.liferay.sync.engine.service.SyncSiteService; import com.liferay.sync.engine.service.SyncWatchEventService; import com.liferay.sync.engine.util.FileKeyUtil; import com.liferay.sync.engine.util.FileUtil; import com.liferay.sync.engine.util.MSOfficeFileUtil; import com.liferay.sync.engine.util.OSDetector; import java.io.IOException; 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.attribute.BasicFileAttributes; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Michael Young * @author Shinn Lok */ public abstract class Watcher implements Runnable { public Watcher(long syncAccountId, Path filePath) { _syncAccountId = syncAccountId; _baseFilePath = filePath; init(); } public void addDeletedFilePathName(String filePathName) { _deletedFilePathNames.add(filePathName); } public void addDownloadedFilePathName(String filePathName) { _downloadedFilePathNames.add(filePathName); } public void addMovedFilePathName(String filePathName) { _movedFilePathNames.add(filePathName); } public void close() { WatcherManager.removeWatcher(_syncAccountId); } public abstract void registerFilePath(Path filePath) throws IOException; public void removeDeletedFilePathName(String filePathName) { _deletedFilePathNames.remove(filePathName); } public void removeDownloadedFilePathName(String filePathName) { _downloadedFilePathNames.remove(filePathName); } public void removeMovedFilePathName(String filePathName) { _movedFilePathNames.remove(filePathName); } public abstract void unregisterFilePath(Path filePath); public void walkFileTree(final Path rootFilePath, final boolean skipRoot) throws IOException { if (isIgnoredFilePath(rootFilePath)) { return; } Files.walkFileTree( rootFilePath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult postVisitDirectory( Path filePath, IOException ioe) throws IOException { if (ioe != null) { _failedFilePaths.add(filePath); return FileVisitResult.CONTINUE; } return super.postVisitDirectory(filePath, null); } @Override public FileVisitResult preVisitDirectory( Path filePath, BasicFileAttributes basicFileAttributes) throws IOException { if (skipRoot && rootFilePath.equals(filePath)) { return FileVisitResult.CONTINUE; } if (filePath.equals(_baseFilePath.resolve(".data")) || isIgnoredFilePath(filePath)) { return FileVisitResult.SKIP_SUBTREE; } SyncFile syncFile = SyncFileService.fetchSyncFile( filePath.toString()); if (syncFile == null) { fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_CREATE, filePath); } try { registerFilePath(filePath); } catch (IOException ioe) { _logger.error(ioe.getMessage(), ioe); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile( Path filePath, BasicFileAttributes basicFileAttributes) throws IOException { if (isIgnoredFilePath(filePath)) { return FileVisitResult.CONTINUE; } SyncFile syncFile = SyncFileService.fetchSyncFile( filePath.toString()); if (syncFile == null) { fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_CREATE, filePath); } else if (FileUtil.isModified(syncFile, filePath)) { fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_MODIFY, filePath); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed( Path filePath, IOException ioe) throws IOException { _failedFilePaths.add(filePath); return FileVisitResult.CONTINUE; } }); } public void watchEvent(String eventType, Path filePath) { addSyncWatchEvent(eventType, filePath); } protected void addCreatedFilePathName(String filePathName) { clearCreatedFilePathNames(); long now = System.currentTimeMillis(); while (_createdFilePathNames.putIfAbsent(now, filePathName) != null) { now++; } } protected synchronized void addSyncWatchEvent( String eventType, Path filePath) { try { String filePathName = filePath.toString(); if (isDuplicateEvent(eventType, filePathName, _syncAccountId)) { return; } SyncAccount syncAccount = SyncAccountService.fetchSyncAccount( _syncAccountId); if (filePathName.equals(syncAccount.getFilePathName())) { return; } Path parentFilePath = filePath.getParent(); String parentFilePathName = parentFilePath.toString(); if (parentFilePathName.equals(syncAccount.getFilePathName())) { SyncSite syncSite = SyncSiteService.fetchSyncSite( filePathName, _syncAccountId); if ((syncSite == null) || syncSite.isActive()) { return; } SyncFile syncFile = SyncFileService.fetchSyncFile(filePathName); if (FileKeyUtil.hasFileKey( filePath, syncFile.getSyncFileId())) { if (_logger.isDebugEnabled()) { _logger.debug( "Sync site {} reactivated.", syncSite.getName()); } SyncSiteService.activateSyncSite( syncSite.getSyncSiteId(), Collections.<SyncFile>emptyList(), false); } return; } long repositoryId = getRepositoryId(filePath); if (repositoryId <= 0) { return; } SyncSite syncSite = SyncSiteService.fetchSyncSite( repositoryId, _syncAccountId); if (!syncSite.isActive()) { return; } if (!eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME_TO)) { if (_deletedFilePathNames.remove(filePath.toString()) || _downloadedFilePathNames.remove(filePath.toString())) { return; } SyncWatchEventService.addSyncWatchEvent( eventType, filePathName, getFileType(eventType, filePath), null, _syncAccountId); return; } String previousEventType = null; Path previousFilePath = null; long previousRepositoryId = 0; SyncWatchEvent lastSyncWatchEvent = SyncWatchEventService.getLastSyncWatchEvent(_syncAccountId); if (lastSyncWatchEvent != null) { previousEventType = lastSyncWatchEvent.getEventType(); previousFilePath = Paths.get( lastSyncWatchEvent.getFilePathName()); previousRepositoryId = getRepositoryId(previousFilePath); } String fileType = getFileType(eventType, filePath); if ((lastSyncWatchEvent == null) || !previousEventType.equals( SyncWatchEvent.EVENT_TYPE_RENAME_FROM) || (previousRepositoryId != repositoryId)) { eventType = SyncWatchEvent.EVENT_TYPE_CREATE; if (_downloadedFilePathNames.remove(filePath.toString())) { return; } SyncWatchEventService.addSyncWatchEvent( eventType, filePathName, getFileType(eventType, filePath), null, _syncAccountId); if (fileType.equals(SyncFile.TYPE_FOLDER)) { SyncFile syncFile = SyncFileService.fetchSyncFile( filePathName); if (syncFile != null) { FileUtil.fireDeleteEvents(Paths.get(filePathName)); } Watcher watcher = WatcherManager.getWatcher(_syncAccountId); watcher.walkFileTree(Paths.get(filePathName), true); } } else if (filePath.equals(previousFilePath)) { lastSyncWatchEvent.setEventType( SyncWatchEvent.EVENT_TYPE_MODIFY); SyncWatchEventService.update(lastSyncWatchEvent); } else if (parentFilePath.equals(previousFilePath.getParent())) { if (MSOfficeFileUtil.isTempRenamedFile( previousFilePath, filePath)) { SyncWatchEventService.deleteSyncWatchEvent( lastSyncWatchEvent.getSyncWatchEventId()); return; } if (_movedFilePathNames.remove(filePath.toString())) { return; } lastSyncWatchEvent.setEventType( SyncWatchEvent.EVENT_TYPE_RENAME); lastSyncWatchEvent.setFilePathName(filePathName); lastSyncWatchEvent.setFileType(fileType); lastSyncWatchEvent.setPreviousFilePathName( previousFilePath.toString()); SyncWatchEventService.update(lastSyncWatchEvent); if (fileType.equals(SyncFile.TYPE_FOLDER)) { Watcher watcher = WatcherManager.getWatcher(_syncAccountId); watcher.walkFileTree(Paths.get(filePathName), true); } } else { SyncFile syncFile = SyncFileService.fetchSyncFile(filePathName); if ((syncFile != null) && fileType.equals(SyncFile.TYPE_FOLDER)) { FileUtil.fireDeleteEvents(Paths.get(filePathName)); Watcher watcher = WatcherManager.getWatcher(_syncAccountId); watcher.walkFileTree(Paths.get(filePathName), true); watchEvent(SyncWatchEvent.EVENT_TYPE_DELETE, filePath); } else { if (_movedFilePathNames.remove(filePath.toString())) { return; } lastSyncWatchEvent.setEventType( SyncWatchEvent.EVENT_TYPE_MOVE); lastSyncWatchEvent.setFilePathName(filePathName); lastSyncWatchEvent.setPreviousFilePathName( previousFilePath.toString()); SyncWatchEventService.update(lastSyncWatchEvent); if (fileType.equals(SyncFile.TYPE_FOLDER)) { Watcher watcher = WatcherManager.getWatcher( _syncAccountId); watcher.walkFileTree(Paths.get(filePathName), true); } } } } catch (Exception e) { _logger.error(e.getMessage(), e); } } protected void clearCreatedFilePathNames() { Map<Long, String> map = _createdFilePathNames.headMap( System.currentTimeMillis() - 5000); map.clear(); } protected void fireWatchEventListener(String eventType, Path filePath) { watchEvent(eventType, filePath); } protected Path getBaseFilePath() { return _baseFilePath; } protected List<Path> getFailedFilePaths() { return _failedFilePaths; } protected String getFileType(String eventType, Path filePath) { if (eventType.equals(SyncWatchEvent.EVENT_TYPE_DELETE) || eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME_FROM)) { SyncFile syncFile = SyncFileService.fetchSyncFile( filePath.toString()); if (syncFile != null) { return syncFile.getType(); } } if (Files.isDirectory(filePath, LinkOption.NOFOLLOW_LINKS)) { return SyncFile.TYPE_FOLDER; } return SyncFile.TYPE_FILE; } protected long getRepositoryId(Path filePath) { while (true) { filePath = filePath.getParent(); if (filePath == null) { return 0; } SyncFile syncFile = SyncFileService.fetchSyncFile( filePath.toString()); if (syncFile != null) { return syncFile.getRepositoryId(); } } } protected abstract void init(); protected boolean isDuplicateEvent( String eventType, String filePathName, long syncAccountId) { SyncWatchEvent lastSyncWatchEvent = SyncWatchEventService.getLastSyncWatchEvent(syncAccountId); if ((lastSyncWatchEvent == null) || !filePathName.equals(lastSyncWatchEvent.getFilePathName()) || !eventType.equals(lastSyncWatchEvent.getEventType())) { return false; } return true; } protected boolean isIgnoredFilePath(Path filePath) { if (FileUtil.notExists(filePath)) { return true; } String fileName = String.valueOf(filePath.getFileName()); if (FileUtil.isIgnoredFilePath(filePath) || FileUtil.isUnsynced(filePath) || (fileName.length() > 255)) { if (_logger.isDebugEnabled()) { _logger.debug("Ignored file path {}", filePath); } return true; } return false; } protected void processFailedFilePaths() throws IOException { List<Path> failedFilePaths = getFailedFilePaths(); for (Path failedFilePath : failedFilePaths) { if (FileUtil.notExists(failedFilePath)) { failedFilePaths.remove(failedFilePath); continue; } if (!Files.isReadable(failedFilePath)) { continue; } failedFilePaths.remove(failedFilePath); if (Files.isDirectory(failedFilePath)) { registerFilePath(failedFilePath); } SyncFile syncFile = SyncFileService.fetchSyncFile( failedFilePath.toString()); if (syncFile == null) { fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_CREATE, failedFilePath); } else if (FileUtil.isModified(syncFile, failedFilePath)) { fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_MODIFY, failedFilePath); } } } protected void processMissingFilePath(Path missingFilePath) { SyncAccount syncAccount = SyncAccountService.fetchSyncAccount( _syncAccountId); Path syncAccountFilePath = Paths.get(syncAccount.getFilePathName()); if (!FileUtil.isRealFilePath(syncAccountFilePath) || !FileUtil.exists(syncAccountFilePath)) { if (_logger.isTraceEnabled()) { _logger.trace( "Missing sync account file path {}", syncAccountFilePath); } syncAccount.setActive(false); syncAccount.setUiEvent( SyncAccount.UI_EVENT_SYNC_ACCOUNT_FOLDER_MISSING); SyncAccountService.update(syncAccount); } else { SyncSite syncSite = SyncSiteService.fetchSyncSite( missingFilePath.toString(), syncAccount.getSyncAccountId()); if ((syncSite != null) && syncSite.isActive() && (!FileUtil.isRealFilePath(syncAccountFilePath) || !FileUtil.exists(missingFilePath))) { if (_logger.isTraceEnabled()) { _logger.trace( "Missing sync site file path {}", missingFilePath); } syncSite.setActive(false); syncSite.setUiEvent(SyncSite.UI_EVENT_SYNC_SITE_FOLDER_MISSING); SyncSiteService.update(syncSite); } } } protected void processWatchEvent(String eventType, Path filePath) throws IOException { if (!OSDetector.isLinux() && filePath.startsWith(_baseFilePath.resolve(".data"))) { return; } _watcherEventsLogger.trace("{}: {}", eventType, filePath); if (eventType.equals(SyncWatchEvent.EVENT_TYPE_CREATE)) { if (isIgnoredFilePath(filePath)) { return; } addCreatedFilePathName(filePath.toString()); fireWatchEventListener(eventType, filePath); if (!OSDetector.isApple() && Files.isDirectory(filePath)) { walkFileTree(filePath, false); } } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_DELETE)) { if (FileUtil.exists(filePath)) { return; } removeCreatedFilePathName(filePath.toString()); if (FileUtil.isIgnoredFileName( String.valueOf(filePath.getFileName())) || FileUtil.isTempFile(filePath)) { return; } processMissingFilePath(filePath); if (FileUtil.notExists(filePath.getParent())) { return; } fireWatchEventListener(SyncWatchEvent.EVENT_TYPE_DELETE, filePath); } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_MODIFY)) { if ((removeCreatedFilePathName(filePath.toString()) && !FileUtil.isValidChecksum(filePath)) || FileUtil.isIgnoredFileName( String.valueOf(filePath.getFileName())) || FileUtil.isTempFile(filePath) || FileUtil.notExists(filePath) || Files.isDirectory(filePath) || FileUtil.isHidden(filePath) || FileUtil.isShortcut(filePath)) { return; } fireWatchEventListener(SyncWatchEvent.EVENT_TYPE_MODIFY, filePath); } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME_FROM)) { removeCreatedFilePathName(filePath.toString()); if (FileUtil.isTempFile(filePath)) { return; } processMissingFilePath(filePath); fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_RENAME_FROM, filePath); } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME_TO)) { if (FileUtil.isIgnoredFileName( String.valueOf(filePath.getFileName())) || FileUtil.isHidden(filePath) || FileUtil.isShortcut(filePath)) { if (_logger.isDebugEnabled()) { _logger.debug("Ignored file path {}", filePath); } return; } fireWatchEventListener( SyncWatchEvent.EVENT_TYPE_RENAME_TO, filePath); } } protected boolean removeCreatedFilePathName(String filePathName) { clearCreatedFilePathNames(); Collection<String> filePathNames = _createdFilePathNames.values(); return filePathNames.remove(filePathName); } private static final Logger _logger = LoggerFactory.getLogger( Watcher.class); private static final Logger _watcherEventsLogger = LoggerFactory.getLogger( "WATCHER_EVENTS_LOGGER"); private final Path _baseFilePath; private final ConcurrentNavigableMap<Long, String> _createdFilePathNames = new ConcurrentSkipListMap<>(); private final List<String> _deletedFilePathNames = new CopyOnWriteArrayList<>(); private final List<String> _downloadedFilePathNames = new CopyOnWriteArrayList<>(); private final List<Path> _failedFilePaths = new CopyOnWriteArrayList<>(); private final List<String> _movedFilePathNames = new CopyOnWriteArrayList<>(); private final long _syncAccountId; }