001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.operations; 019 020import java.util.Arrays; 021import java.util.List; 022import java.util.Map; 023import java.util.logging.Level; 024import java.util.logging.Logger; 025 026import org.syncany.config.Config; 027import org.syncany.config.LocalEventBus; 028import org.syncany.plugins.transfer.StorageException; 029import org.syncany.plugins.transfer.TransferManager; 030import org.syncany.plugins.transfer.TransferManagerFactory; 031import org.syncany.plugins.transfer.features.ReadAfterWriteConsistent; 032import org.syncany.plugins.transfer.features.PathAware; 033import org.syncany.plugins.transfer.features.Retriable; 034import org.syncany.plugins.transfer.features.TransactionAware; 035import org.syncany.plugins.transfer.features.TransactionAwareFeatureTransferManager; 036import org.syncany.plugins.transfer.files.ActionRemoteFile; 037import org.syncany.plugins.transfer.files.CleanupRemoteFile; 038import org.syncany.plugins.transfer.files.DatabaseRemoteFile; 039 040/** 041 * Represents and is inherited by a transfer operation. Transfer operations are operations 042 * that modify the repository and/or are relevant for the consistency of the local directory 043 * or the remote repository. 044 * 045 * <p>This abstract class offers convenience methods to handle {@link ActionRemoteFile} as well 046 * as to handle the connection and local cache. 047 * 048 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 049 */ 050public abstract class AbstractTransferOperation extends Operation { 051 private static final Logger logger = Logger.getLogger(AbstractTransferOperation.class.getSimpleName()); 052 053 /** 054 * Defines the time after which old/outdated action files from other clients are 055 * deleted. This time must be significantly larger than the time action files are 056 * renewed by the {@link ActionFileHandler}. 057 * 058 * @see ActionFileHandler#ACTION_RENEWAL_INTERVAL 059 */ 060 private static final int ACTION_FILE_DELETE_TIME = ActionFileHandler.ACTION_RENEWAL_INTERVAL + 5 * 60 * 1000; // Minutes 061 062 protected TransactionAwareFeatureTransferManager transferManager; 063 protected ActionFileHandler actionHandler; 064 065 protected LocalEventBus eventBus; 066 067 public AbstractTransferOperation(Config config, String operationName) { 068 super(config); 069 070 this.eventBus = LocalEventBus.getInstance(); 071 072 try { 073 // Do NOT reuse TransferManager for action file renewal; see #140 074 075 TransferManager actionFileTransferManager = TransferManagerFactory 076 .build(config) 077 .withFeature(ReadAfterWriteConsistent.class) 078 .withFeature(Retriable.class) 079 .asDefault(); 080 081 TransactionAwareFeatureTransferManager regularFileTransferManager = TransferManagerFactory 082 .build(config) 083 .withFeature(ReadAfterWriteConsistent.class) 084 .withFeature(Retriable.class) 085 .withFeature(PathAware.class) 086 .withFeature(TransactionAware.class) 087 .as(TransactionAware.class); 088 089 this.actionHandler = new ActionFileHandler(actionFileTransferManager, operationName, config.getMachineName()); 090 this.transferManager = regularFileTransferManager; 091 } 092 catch (StorageException e) { 093 logger.log(Level.SEVERE, "Unable to create AbstractTransferOperation: Unable to create TransferManager", e); 094 throw new RuntimeException("Unable to create AbstractTransferOperation: Unable to create TransferManager: " + e.getMessage()); 095 } 096 } 097 098 protected void startOperation() throws Exception { 099 actionHandler.start(); 100 } 101 102 protected void finishOperation() throws StorageException { 103 actionHandler.finish(); 104 105 cleanActionFiles(); 106 disconnectTransferManager(); 107 clearCache(); 108 } 109 110 protected boolean otherRemoteOperationsRunning(String... operationIdentifiers) throws StorageException { 111 logger.log(Level.INFO, "Looking for other running remote operations ..."); 112 Map<String, ActionRemoteFile> actionRemoteFiles = transferManager.list(ActionRemoteFile.class); 113 114 boolean otherRemoteOperationsRunning = false; 115 List<String> disallowedOperationIdentifiers = Arrays.asList(operationIdentifiers); 116 117 for (ActionRemoteFile actionRemoteFile : actionRemoteFiles.values()) { 118 String operationName = actionRemoteFile.getOperationName(); 119 String machineName = actionRemoteFile.getClientName(); 120 121 boolean isOwnActionFile = machineName.equals(config.getMachineName()); 122 boolean isOperationAllowed = !disallowedOperationIdentifiers.contains(operationName); 123 boolean isOutdatedActionFile = isOutdatedActionFile(actionRemoteFile); 124 125 if (!isOwnActionFile) { 126 if (!isOutdatedActionFile) { 127 if (isOperationAllowed) { 128 logger.log(Level.INFO, "- Action file from other client, but allowed operation; not marking running; " + actionRemoteFile); 129 } 130 else { 131 logger.log(Level.INFO, "- Action file from other client; --> marking operations running (!); " + actionRemoteFile); 132 otherRemoteOperationsRunning = true; 133 } 134 } 135 else { 136 logger.log(Level.INFO, "- Action file outdated; ignoring " + actionRemoteFile); 137 } 138 } 139 } 140 141 return otherRemoteOperationsRunning; 142 } 143 144 private void cleanActionFiles() throws StorageException { 145 logger.log(Level.INFO, "Cleaning own old action files ..."); 146 Map<String, ActionRemoteFile> actionRemoteFiles = transferManager.list(ActionRemoteFile.class); 147 148 for (ActionRemoteFile actionRemoteFile : actionRemoteFiles.values()) { 149 String machineName = actionRemoteFile.getClientName(); 150 151 boolean isOwnActionFile = machineName.equals(config.getMachineName()); 152 boolean isOutdatedActionFile = isOutdatedActionFile(actionRemoteFile); 153 154 if (isOwnActionFile) { 155 logger.log(Level.INFO, "- Deleting own action file " + actionRemoteFile + " ..."); 156 transferManager.delete(actionRemoteFile); 157 } 158 else if (isOutdatedActionFile) { 159 logger.log(Level.INFO, "- Action file from other client is OUTDATED; deleting " + actionRemoteFile + " ..."); 160 transferManager.delete(actionRemoteFile); 161 } 162 else { 163 logger.log(Level.INFO, "- Action file is current; ignoring " + actionRemoteFile + " ..."); 164 } 165 } 166 } 167 168 /** 169 * This method is used to determine how a database file should be named when 170 * it is about to be uploaded. It returns the number of the newest database file (which is the 171 * highest number). 172 * 173 * @param client name of the client for which we want to upload a database version. 174 * @param knownDatabases all DatabaseRemoteFiles present in the repository 175 * @return the largest database fileversion number. 176 */ 177 protected long getNewestDatabaseFileVersion(String client, List<DatabaseRemoteFile> knownDatabases) { 178 // TODO [low] This could be done via the "known_databases" database table 179 180 // Obtain last known database file version number and increment it 181 long clientVersion = 0; 182 183 for (DatabaseRemoteFile databaseRemoteFile : knownDatabases) { 184 if (databaseRemoteFile.getClientName().equals(client)) { 185 clientVersion = Math.max(clientVersion, databaseRemoteFile.getClientVersion()); 186 } 187 } 188 189 return clientVersion; 190 } 191 192 protected long getLastRemoteCleanupNumber(Map<String, CleanupRemoteFile> cleanupFiles) { 193 long cleanupNumber = 0; 194 195 // Find the number of the last cleanup 196 for (CleanupRemoteFile cleanupRemoteFile : cleanupFiles.values()) { 197 cleanupNumber = Math.max(cleanupNumber, cleanupRemoteFile.getCleanupNumber()); 198 } 199 200 return cleanupNumber; 201 } 202 203 private boolean isOutdatedActionFile(ActionRemoteFile actionFile) { 204 // TODO [low] Even though this is UTC and the times frames are large, this might be an issue with different timezones or wrong system clocks 205 return System.currentTimeMillis() - ACTION_FILE_DELETE_TIME > actionFile.getTimestamp(); 206 } 207 208 private void disconnectTransferManager() { 209 try { 210 transferManager.disconnect(); 211 } 212 catch (StorageException e) { 213 logger.log(Level.FINE, "Could not disconnect the transfermanager", e); 214 } 215 } 216 217 private void clearCache() { 218 config.getCache().clear(); 219 } 220}