/** * 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; import com.j256.ormlite.support.ConnectionSource; import com.liferay.sync.encryptor.SyncEncryptor; import com.liferay.sync.engine.document.library.util.FileEventUtil; import com.liferay.sync.engine.document.library.util.ServerEventUtil; import com.liferay.sync.engine.file.system.SyncWatchEventProcessor; import com.liferay.sync.engine.file.system.Watcher; import com.liferay.sync.engine.file.system.util.WatcherManager; import com.liferay.sync.engine.lan.server.LanEngine; import com.liferay.sync.engine.model.SyncAccount; import com.liferay.sync.engine.model.SyncFile; import com.liferay.sync.engine.model.SyncProp; import com.liferay.sync.engine.model.SyncSite; import com.liferay.sync.engine.model.SyncUser; import com.liferay.sync.engine.service.SyncAccountService; import com.liferay.sync.engine.service.SyncFileService; import com.liferay.sync.engine.service.SyncPropService; import com.liferay.sync.engine.service.SyncSiteService; import com.liferay.sync.engine.service.SyncUserService; import com.liferay.sync.engine.service.SyncWatchEventService; import com.liferay.sync.engine.service.persistence.SyncAccountPersistence; import com.liferay.sync.engine.upgrade.util.UpgradeUtil; import com.liferay.sync.engine.util.ConnectionRetryUtil; import com.liferay.sync.engine.util.FileKeyUtil; import com.liferay.sync.engine.util.FileLockRetryUtil; import com.liferay.sync.engine.util.FileUtil; import com.liferay.sync.engine.util.LoggerUtil; import com.liferay.sync.engine.util.PropsKeys; import com.liferay.sync.engine.util.PropsUtil; import com.liferay.sync.engine.util.SyncEngineUtil; import com.liferay.sync.engine.util.Validator; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Shinn Lok */ public class SyncEngine { public static synchronized void cancelSyncAccountTasks(long syncAccountId) { if (!_running) { return; } Object[] syncAccountTasks = _syncAccountTasks.get(syncAccountId); if (syncAccountTasks == null) { return; } Watcher watcher = (Watcher)syncAccountTasks[0]; watcher.close(); ScheduledFuture<?> localEventsScheduledFuture = (ScheduledFuture<?>)syncAccountTasks[1]; localEventsScheduledFuture.cancel(true); ScheduledFuture<?> remoteEventsScheduledFuture = (ScheduledFuture<?>)syncAccountTasks[2]; remoteEventsScheduledFuture.cancel(true); } public static ExecutorService getExecutorService() { if (_threadPoolExecutor != null) { return _threadPoolExecutor; } _threadPoolExecutor = new ThreadPoolExecutor( 64, 64, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); _threadPoolExecutor.allowCoreThreadTimeOut(true); return _threadPoolExecutor; } public static synchronized boolean isRunning() { return _running; } public static synchronized void scheduleSyncAccountTasks( final long syncAccountId) { Runnable runnable = new Runnable() { @Override public void run() { try { doScheduleSyncAccountTasks(syncAccountId); } catch (Exception e) { _logger.error(e.getMessage(), e); } } }; ExecutorService executorService = getExecutorService(); executorService.execute(runnable); } public static synchronized void start() { if (_running) { return; } try { doStart(); } catch (Exception e) { _logger.error(e.getMessage(), e); } } public static synchronized void stop() { if (!_running) { return; } try { doStop(); } catch (Exception e) { _logger.error(e.getMessage(), e); } } protected static void configureSyncAccounts() throws Exception { for (int i = 0;; i++) { String postfix = "." + i; String filePathName = FileUtil.getFilePathName( PropsUtil.get(PropsKeys.SYNC_ACCOUNT_FILE_PATH_NAME + postfix)); if (Validator.isBlank(filePathName)) { break; } String login = PropsUtil.get( PropsKeys.SYNC_ACCOUNT_LOGIN + postfix); String password = PropsUtil.get( PropsKeys.SYNC_ACCOUNT_PASSWORD + postfix); String pluginVersion = PropsUtil.get( PropsKeys.SYNC_ACCOUNT_PLUGIN_VERSION + postfix); String url = PropsUtil.get(PropsKeys.SYNC_ACCOUNT_URL + postfix); SyncAccount syncAccount = SyncAccountService.fetchSyncAccountByFilePathName(filePathName); if (syncAccount != null) { syncAccount.setLogin(login); syncAccount.setPassword(SyncEncryptor.encrypt(password)); syncAccount.setPluginVersion(pluginVersion); syncAccount.setUrl(url); SyncAccountService.update(syncAccount); } else { syncAccount = SyncAccountService.addSyncAccount( filePathName, login, password, pluginVersion, url); SyncUser syncUser = new SyncUser(); syncUser.setSyncAccountId(syncAccount.getSyncAccountId()); SyncUserService.update(syncUser); } syncAccount = ServerEventUtil.synchronizeSyncAccount( syncAccount.getSyncAccountId()); ServerEventUtil.synchronizeSyncSites( syncAccount.getSyncAccountId()); String[] sites = PropsUtil.getArray( PropsKeys.SYNC_ACCOUNT_SITES + postfix); for (String site : sites) { SyncSite syncSite = SyncSiteService.fetchSyncSite( FileUtil.getFilePathName( syncAccount.getFilePathName(), site), syncAccount.getSyncAccountId()); if (syncSite == null) { try { syncSite = SyncSiteService.fetchSyncSite( Long.valueOf(site), syncAccount.getSyncAccountId()); } catch (NumberFormatException nfe) { } } if (syncSite == null) { continue; } SyncSiteService.activateSyncSite( syncSite.getSyncSiteId(), Collections.<SyncFile>emptyList(), false); } } } protected static void doScheduleSyncAccountTasks(long syncAccountId) throws Exception { if (!_running) { return; } SyncAccount syncAccount = ServerEventUtil.synchronizeSyncAccount( syncAccountId); syncAccount.setState(SyncAccount.STATE_CONNECTED); syncAccount.setUiEvent(SyncAccount.UI_EVENT_NONE); SyncAccountService.update(syncAccount); ServerEventUtil.registerSyncDevice(syncAccountId); Path syncAccountFilePath = Paths.get(syncAccount.getFilePathName()); SyncFile syncAccountFile = SyncFileService.fetchSyncFile( syncAccount.getFilePathName()); if (!FileKeyUtil.hasFileKey( syncAccountFilePath, syncAccountFile.getSyncFileId())) { 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); return; } else if (!syncAccount.isActive()) { SyncAccountService.activateSyncAccount(syncAccountId, false); return; } List<SyncSite> syncSites = SyncSiteService.findSyncSites(syncAccountId); for (SyncSite syncSite : syncSites) { syncSite.setState(SyncSite.STATE_SYNCED); SyncSiteService.update(syncSite); if (!syncSite.isActive() || (syncSite.getRemoteSyncTime() == -1)) { continue; } Path syncSiteFilePath = Paths.get(syncSite.getFilePathName()); if (FileUtil.notExists(syncSiteFilePath)) { if (_logger.isTraceEnabled()) { _logger.trace( "Missing sync site file path {}", syncSiteFilePath); } syncSite.setActive(false); syncSite.setUiEvent(SyncSite.UI_EVENT_SYNC_SITE_FOLDER_MISSING); SyncSiteService.update(syncSite); } } if (!ConnectionRetryUtil.retryInProgress(syncAccountId)) { ServerEventUtil.synchronizeSyncSites(syncAccountId); } SyncWatchEventService.deleteSyncWatchEvents(syncAccountId); Watcher watcher = WatcherManager.getWatcher(syncAccountId); watcher.walkFileTree(syncAccountFilePath, false); SyncWatchEventProcessor syncWatchEventProcessor = new SyncWatchEventProcessor(syncAccountId); syncWatchEventProcessor.run(); if (!ConnectionRetryUtil.retryInProgress(syncAccountId)) { synchronizeSyncFiles(syncAccountId); } scheduleEvents(syncAccount, syncWatchEventProcessor, watcher); } protected static void doStart() throws Exception { _running = true; SyncEngineUtil.fireSyncEngineStateChanged( SyncEngineUtil.SYNC_ENGINE_STATE_STARTING); LoggerUtil.init(); _logger.info("Starting Sync engine"); UpgradeUtil.upgrade(); FileLockRetryUtil.init(); configureSyncAccounts(); for (SyncAccount syncAccount : SyncAccountService.findAll()) { scheduleSyncAccountTasks(syncAccount.getSyncAccountId()); } if (SyncPropService.getBoolean(SyncProp.KEY_LAN_ENABLED, true)) { LanEngine.start(); } SyncEngineUtil.fireSyncEngineStateChanged( SyncEngineUtil.SYNC_ENGINE_STATE_STARTED); } protected static void doStop() throws Exception { SyncEngineUtil.fireSyncEngineStateChanged( SyncEngineUtil.SYNC_ENGINE_STATE_STOPPING); _logger.info("Stopping Sync engine"); for (long syncAccountId : _syncAccountTasks.keySet()) { cancelSyncAccountTasks(syncAccountId); } if (_threadPoolExecutor != null) { _threadPoolExecutor.shutdownNow(); } _localEventsScheduledExecutorService.shutdownNow(); _remoteEventsScheduledExecutorService.shutdownNow(); FileLockRetryUtil.shutdown(); LanEngine.stop(); LoggerUtil.shutdown(); SyncAccountPersistence syncAccountPersistence = SyncAccountService.getSyncAccountPersistence(); ConnectionSource connectionSource = syncAccountPersistence.getConnectionSource(); connectionSource.closeQuietly(); SyncEngineUtil.fireSyncEngineStateChanged( SyncEngineUtil.SYNC_ENGINE_STATE_STOPPED); _running = false; } protected static void scheduleEvents( final SyncAccount syncAccount, final SyncWatchEventProcessor syncWatchEventProcessor, Watcher watcher) { ExecutorService executorService = getExecutorService(); executorService.execute(watcher); ScheduledFuture<?> localEventsScheduledFuture = _localEventsScheduledExecutorService.scheduleWithFixedDelay( syncWatchEventProcessor, 0, 3, TimeUnit.SECONDS); Runnable runnable = new Runnable() { @Override public void run() { try { doRun(); } catch (Exception e) { _logger.error(e.getMessage(), e); } } protected void doRun() { SyncAccount updatedSyncAccount = SyncAccountService.fetchSyncAccount( syncAccount.getSyncAccountId()); if ((updatedSyncAccount.getState() != SyncAccount.STATE_CONNECTED) || syncWatchEventProcessor.isInProgress()) { return; } Set<Long> syncSiteIds = SyncSiteService.getActiveSyncSiteIds( syncAccount.getSyncAccountId()); for (long syncSiteId : new HashSet<>(syncSiteIds)) { SyncSite syncSite = SyncSiteService.fetchSyncSite( syncSiteId); if (syncSite.getState() == SyncSite.STATE_IN_PROGRESS) { continue; } FileEventUtil.getUpdates( syncSite.getGroupId(), syncAccount.getSyncAccountId(), syncSite, true); } } }; ScheduledFuture<?> remoteEventsScheduledFuture = _remoteEventsScheduledExecutorService.scheduleWithFixedDelay( runnable, 0, syncAccount.getPollInterval(), TimeUnit.SECONDS); _syncAccountTasks.put( syncAccount.getSyncAccountId(), new Object[] { watcher, localEventsScheduledFuture, remoteEventsScheduledFuture }); } protected static void synchronizeSyncFiles(long syncAccountId) throws IOException { List<SyncSite> syncSites = SyncSiteService.findSyncSites(syncAccountId); for (SyncSite syncSite : syncSites) { if (!syncSite.isActive()) { continue; } FileUtil.fireDeleteEvents(Paths.get(syncSite.getFilePathName())); } FileEventUtil.retryFileTransfers(syncAccountId); } private static final Logger _logger = LoggerFactory.getLogger( SyncEngine.class); private static final ScheduledExecutorService _localEventsScheduledExecutorService = Executors.newScheduledThreadPool( 5); private static final ScheduledExecutorService _remoteEventsScheduledExecutorService = Executors.newScheduledThreadPool(5); private static boolean _running; private static final Map<Long, Object[]> _syncAccountTasks = new HashMap<>(); private static ThreadPoolExecutor _threadPoolExecutor; }