/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.syncany.plugins.transfer;
import java.io.File;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.syncany.plugins.transfer.files.RemoteFile;
/**
* The retriable transfer manager implements a simple try-sleep-retry mechanism
* for regular {@link TransferManager}s. It encapsules a single transfer manager and
* proxies all of its methods. If a method fails with a {@link StorageException}, the
* method is retried N times before the exception is actually thrown to the caller.
* Between retries, the method waits M seconds.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class RetriableTransferManager implements TransferManager {
private static final Logger logger = Logger.getLogger(RetriableTransferManager.class.getSimpleName());
// Values are public to enable quicker testing
private static int RETRY_MAX_COUNT = 3;
public static int RETRY_SLEEP_MILLIS = 3000;
private interface RetriableMethod {
public Object execute() throws StorageException;
}
private TransferManager underlyingTransferManager;
private int tryCount;
public RetriableTransferManager(TransferManager underlyingTransferManager) {
this.underlyingTransferManager = underlyingTransferManager;
this.tryCount = 0;
}
@Override
public void connect() throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.connect();
return null;
}
});
}
@Override
public void disconnect() throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.disconnect();
return null;
}
});
}
@Override
public void init(final boolean createIfRequired) throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.init(createIfRequired);
return null;
}
});
}
@Override
public void download(final RemoteFile remoteFile, final File localFile) throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.download(remoteFile, localFile);
return null;
}
});
}
@Override
public void move(final RemoteFile sourceFile, final RemoteFile targetFile) throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.move(sourceFile, targetFile);
return null;
}
});
}
@Override
public void upload(final File localFile, final RemoteFile remoteFile) throws StorageException {
retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
underlyingTransferManager.upload(localFile, remoteFile);
return null;
}
});
}
@Override
public boolean delete(final RemoteFile remoteFile) throws StorageException {
return (Boolean) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.delete(remoteFile);
}
});
}
@Override
@SuppressWarnings("unchecked")
public <T extends RemoteFile> Map<String, T> list(final Class<T> remoteFileClass) throws StorageException {
return (Map<String, T>) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.list(remoteFileClass);
}
});
}
@Override
public StorageTestResult test(boolean testCreateTarget) {
return underlyingTransferManager.test(testCreateTarget);
}
@Override
public boolean testTargetExists() throws StorageException {
return (Boolean) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.testTargetExists();
}
});
}
@Override
public boolean testTargetCanWrite() throws StorageException {
return (Boolean) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.testTargetCanWrite();
}
});
}
@Override
public boolean testTargetCanCreate() throws StorageException {
return (Boolean) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.testTargetCanCreate();
}
});
}
@Override
public boolean testRepoFileExists() throws StorageException {
return (Boolean) retryMethod(new RetriableMethod() {
@Override
public Object execute() throws StorageException {
return underlyingTransferManager.testRepoFileExists();
}
});
}
private Object retryMethod(RetriableMethod retryableMethod) throws StorageException {
tryCount = 0;
while (true) {
try {
if (tryCount > 0) {
logger.log(Level.WARNING, "Retrying method: " + tryCount + "/" + RETRY_MAX_COUNT + " ...");
}
Object result = retryableMethod.execute();
tryCount = 0;
return result;
}
catch (StorageMoveException | StorageFileNotFoundException e) {
logger.log(Level.INFO, "StorageException caused by missing file, not the connection. Not retrying.");
throw e;
}
catch (StorageException e) {
tryCount++;
if (tryCount >= RETRY_MAX_COUNT) {
logger.log(Level.WARNING, "Transfer method failed. No retries left. Throwing exception.", e);
throw e;
}
else {
logger.log(Level.WARNING, "Transfer method failed. " + tryCount + "/" + RETRY_MAX_COUNT + " retries. Sleeping "
+ RETRY_SLEEP_MILLIS
+ "ms ...", e);
try {
Thread.sleep(RETRY_SLEEP_MILLIS);
}
catch (Exception e1) {
throw new StorageException(e1);
}
}
}
}
}
}