/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2016 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.features;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.syncany.config.Config;
import org.syncany.plugins.transfer.StorageException;
import org.syncany.plugins.transfer.StorageTestResult;
import org.syncany.plugins.transfer.TransferManager;
import org.syncany.plugins.transfer.files.RemoteFile;
import org.syncany.util.ReflectionUtil;
/**
* <p>The ReadWriteConsistentFeatureTransferManager waits specific amount of time after
* {@link #upload(File, RemoteFile)} and {@link #move(RemoteFile, RemoteFile)} operations
* because some storage backends do no guarantee that a file immediately exists after
* creation.
*
* <p>It throttles existence check using a simple exponential method:<br/>
*
* <code>throttle(n) = 3 ^ n * 100 ms, with n being the current iteration
*
* @author Christian Roth <christian.roth@port17.de>
*/
public class ReadAfterWriteConsistentFeatureTransferManager implements FeatureTransferManager {
private static final Logger logger = Logger.getLogger(ReadAfterWriteConsistentFeatureTransferManager.class.getSimpleName());
private final TransferManager underlyingTransferManager;
private final Throttler throttler;
private final ReadAfterWriteConsistentFeatureExtension readAfterWriteConsistentFeatureExtension;
public ReadAfterWriteConsistentFeatureTransferManager(TransferManager originalTransferManager, TransferManager underlyingTransferManager, Config config, ReadAfterWriteConsistent readAfterWriteConsistentAnnotation) {
this.underlyingTransferManager = underlyingTransferManager;
this.throttler = new Throttler(readAfterWriteConsistentAnnotation.maxRetries(), readAfterWriteConsistentAnnotation.maxWaitTime());
this.readAfterWriteConsistentFeatureExtension = getReadAfterWriteConsistentFeatureExtension(originalTransferManager, readAfterWriteConsistentAnnotation);
}
@SuppressWarnings("unchecked")
private ReadAfterWriteConsistentFeatureExtension getReadAfterWriteConsistentFeatureExtension(TransferManager originalTransferManager, ReadAfterWriteConsistent readAfterWriteConsistentAnnotation) {
Class<? extends TransferManager> originalTransferManagerClass = originalTransferManager.getClass();
Class<ReadAfterWriteConsistentFeatureExtension> readAfterWriteConsistentFeatureExtensionClass = (Class<ReadAfterWriteConsistentFeatureExtension>) readAfterWriteConsistentAnnotation.extension();
try {
Constructor<?> constructor = ReflectionUtil.getMatchingConstructorForClass(readAfterWriteConsistentFeatureExtensionClass, originalTransferManagerClass);
if (constructor != null) {
return (ReadAfterWriteConsistentFeatureExtension) constructor.newInstance(originalTransferManager);
}
return readAfterWriteConsistentFeatureExtensionClass.newInstance();
}
catch (InvocationTargetException | InstantiationException | IllegalAccessException | NullPointerException e) {
throw new RuntimeException("Cannot instantiate ReadWriteConsistentFeatureExtension (perhaps " + readAfterWriteConsistentFeatureExtensionClass + " does not exist?)", e);
}
}
@Override
public void connect() throws StorageException {
underlyingTransferManager.connect();
}
@Override
public void disconnect() throws StorageException {
underlyingTransferManager.disconnect();
}
@Override
public void init(final boolean createIfRequired) throws StorageException {
underlyingTransferManager.init(createIfRequired);
}
@Override
public void download(final RemoteFile remoteFile, final File localFile) throws StorageException {
underlyingTransferManager.download(remoteFile, localFile);
}
@Override
public void move(final RemoteFile sourceFile, final RemoteFile targetFile) throws StorageException {
underlyingTransferManager.move(sourceFile, targetFile);
waitForFile(targetFile);
}
@Override
public void upload(final File localFile, final RemoteFile remoteFile) throws StorageException {
underlyingTransferManager.upload(localFile, remoteFile);
waitForFile(remoteFile);
}
@Override
public boolean delete(final RemoteFile remoteFile) throws StorageException {
return underlyingTransferManager.delete(remoteFile);
}
@Override
public <T extends RemoteFile> Map<String, T> list(final Class<T> remoteFileClass) throws StorageException {
return underlyingTransferManager.list(remoteFileClass);
}
@Override
public String getRemoteFilePath(Class<? extends RemoteFile> remoteFileClass) {
return underlyingTransferManager.getRemoteFilePath(remoteFileClass);
}
@Override
public StorageTestResult test(boolean testCreateTarget) {
return underlyingTransferManager.test(testCreateTarget);
}
@Override
public boolean testTargetExists() throws StorageException {
return underlyingTransferManager.testTargetExists();
}
@Override
public boolean testTargetCanWrite() throws StorageException {
return underlyingTransferManager.testTargetCanWrite();
}
@Override
public boolean testTargetCanCreate() throws StorageException {
return underlyingTransferManager.testTargetCanCreate();
}
@Override
public boolean testRepoFileExists() throws StorageException {
return underlyingTransferManager.testRepoFileExists();
}
private void waitForFile(RemoteFile remoteFile) throws StorageException {
while (true) {
if (readAfterWriteConsistentFeatureExtension.exists(remoteFile)) {
logger.log(Level.FINER, remoteFile + " exists on the remote side");
throttler.reset();
break;
}
try {
long waitForMs = throttler.next();
logger.log(Level.FINER, "File not found on the remote side, perhaps its in transit, waiting " + waitForMs + "ms ...");
Thread.sleep(waitForMs);
}
catch (InterruptedException e) {
throw new StorageException("Unable to wait anymore", e);
}
}
}
private class Throttler {
private final int maxRetries;
private final int maxWait;
private int currentIteration = 0;
public Throttler(int maxRetries, int maxWait) {
this.maxRetries = maxRetries;
this.maxWait = maxWait;
}
public long next() throws InterruptedException {
long waitFor = (long) Math.pow(3, currentIteration++) * 100;
if (waitFor > maxWait || currentIteration > maxRetries) {
throw new InterruptedException("Unable to wait anymore, because ending criteria reached");
}
return waitFor;
}
public void reset() {
currentIteration = 0;
}
}
}