package org.peerbox.watchservice.states;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.hive2hive.core.exceptions.NoPeerConnectionException;
import org.hive2hive.core.exceptions.NoSessionException;
import org.hive2hive.processframework.exceptions.InvalidProcessStateException;
import org.hive2hive.processframework.exceptions.ProcessExecutionException;
import org.peerbox.app.manager.ProcessHandle;
import org.peerbox.app.manager.file.IFileManager;
import org.peerbox.watchservice.IAction;
import org.peerbox.watchservice.IFileEventManager;
import org.peerbox.watchservice.filetree.IFileTree;
import org.peerbox.watchservice.filetree.composite.FileComponent;
import org.peerbox.watchservice.filetree.composite.FileLeaf;
import org.peerbox.watchservice.filetree.composite.FolderComposite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.SetMultimap;
/**
* Base class for the concrete states of the state pattern.
*
* @author winzenried
*
*/
public abstract class AbstractActionState {
private final static Logger logger = LoggerFactory.getLogger(AbstractActionState.class);
protected IAction action;
protected StateType type = StateType.ABSTRACT;
protected ProcessHandle<Void> handle;
/*
* Execution and notification related functions
*/
public abstract ExecutionHandle execute(IFileManager fileManager) throws NoSessionException,
NoPeerConnectionException, InvalidProcessStateException, ProcessExecutionException;
public AbstractActionState(IAction action, StateType type) {
this.action = action;
this.type = type;
}
public StateType getStateType(){
return type;
}
public AbstractActionState getDefaultState(){
return new EstablishedState(action);
}
/*
* LOCAL state changers
*/
public AbstractActionState changeStateOnLocalCreate(){
logStateTransition(getStateType(), EventType.LOCAL_CREATE, StateType.LOCAL_CREATE);
return new LocalCreateState(action);
}
public AbstractActionState changeStateOnLocalDelete(){
logStateTransition(getStateType(), EventType.LOCAL_DELETE, StateType.INITIAL);
return new InitialState(action);
}
public AbstractActionState changeStateOnLocalUpdate(){
logStateTransition(getStateType(), EventType.LOCAL_UPDATE, StateType.LOCAL_UPDATE);
return new LocalUpdateState(action);
}
public AbstractActionState changeStateOnLocalMove(Path oldPath){
logStateTransition(getStateType(), EventType.LOCAL_MOVE, StateType.LOCAL_MOVE);
return new LocalMoveState(action, oldPath);
}
public AbstractActionState changeStateOnLocalHardDelete(){
logStateTransition(getStateType(), EventType.LOCAL_HARD_DELETE, StateType.LOCAL_HARD_DELETE);
return new LocalHardDeleteState(action);
}
/*
* REMOTE state changers
*/
public AbstractActionState changeStateOnRemoteDelete(){
logStateTransition(getStateType(), EventType.REMOTE_DELETE, StateType.INITIAL);
return new InitialState(action);
}
public AbstractActionState changeStateOnRemoteCreate(){
logStateTransition(getStateType(), EventType.REMOTE_CREATE, StateType.REMOTE_CREATE);
return new RemoteCreateState(action);
}
public AbstractActionState changeStateOnRemoteUpdate(){
logStateTransition(getStateType(), EventType.REMOTE_UPDATE, StateType.REMOTE_UPDATE);
return new RemoteUpdateState(action);
}
public AbstractActionState changeStateOnRemoteMove(Path oldFilePath) {
logStateTransition(getStateType(), EventType.REMOTE_MOVE, StateType.ESTABLISHED);
return new EstablishedState(action);
}
public AbstractActionState handleLocalCreate() {
action.updateTimeAndQueue();
return changeStateOnLocalCreate();
}
public AbstractActionState handleLocalHardDelete(){
action.updateTimeAndQueue();
return changeStateOnLocalHardDelete();
}
public AbstractActionState handleLocalDelete(){
IFileTree fileTree = action.getFileEventManager().getFileTree();
FileComponent file = action.getFile();
action.updateTimeAndQueue();
file.setIsSynchronized(false);
if(file.isFile()){
FileLeaf moveTarget = fileTree.findCreatedByContent((FileLeaf)file);
if(moveTargetIsValid(moveTarget)){
return performSwappedMove(moveTarget);
} else if(file.isUploaded()){
putToFileMoveSources((FileLeaf)file);
}
} else {
FileComponent moveTarget = fileTree.findCreatedByStructure((FolderComposite)file);
if(moveTargetIsValid(moveTarget)){
return performSwappedMove(moveTarget);
} else if(file.isUploaded()){
putToFolderMoveSources((FolderComposite)file);
}
}
file.getParent().updateContentHash();
file.getParent().updateStructureHash();
if(file.isFolder()){
((FolderComposite)file).updateStateOnLocalDelete();
}
return this.changeStateOnLocalDelete();
}
public AbstractActionState handleLocalUpdate() {
action.updateTimeAndQueue();
return changeStateOnLocalUpdate();
}
public AbstractActionState handleLocalMove(Path newPath) {
Path oldPath = Paths.get(action.getFile().getPath().toString());
if(action.getFile().isFolder()){
((FolderComposite)action.getFile()).setIsSynchronizedRecursively(true);
} else {
((FileLeaf)action.getFile()).setIsSynchronized(true);
}
action.getFileEventManager().getFileTree().putFile(newPath, action.getFile());
action.updateTimeAndQueue();
return changeStateOnLocalMove(oldPath);
}
/*
* REMOTE event handler
*/
public AbstractActionState handleRemoteCreate(){
action.updateTimeAndQueue();
return changeStateOnRemoteCreate();
}
public AbstractActionState handleRemoteDelete() {
IFileEventManager eventManager = action.getFileEventManager();
eventManager.getFileTree().deleteFile(action.getFile().getPath());
eventManager.getFileComponentQueue().remove(action.getFile());
try {
java.nio.file.Files.delete(action.getFile().getPath());
} catch (IOException e) {
logger.warn("Could not delete file {} ({}).",
action.getFile().getPath(), e.getMessage(), e);
}
return changeStateOnRemoteDelete();
}
public AbstractActionState handleRemoteUpdate() {
action.updateTimeAndQueue();
return changeStateOnRemoteUpdate();
}
public AbstractActionState handleRemoteMove(Path destPath) {
final IFileEventManager eventManager = action.getFileEventManager();
final IFileTree fileTree = eventManager.getFileTree();
final FileComponent file = action.getFile();
eventManager.getFileComponentQueue().remove(file);
Path sourcePath = file.getPath();
fileTree.deleteFile(file.getPath());
fileTree.putFile(destPath, file);
return changeStateOnRemoteMove(sourcePath);
}
private boolean moveTargetIsValid(FileComponent moveTarget){
return moveTarget != null && moveTarget.getPath().toFile().exists();
}
private void putToFolderMoveSources(FolderComposite file) {
final IFileTree fileTree = action.getFileEventManager().getFileTree();
SetMultimap<String, FolderComposite> deletedFolders = fileTree.getDeletedByStructureHash();
logger.trace("Delete folder: put folder {} with structure hash {} to deleted folders.", file.getPath(), file.getStructureHash());
deletedFolders.put(file.getStructureHash(), (FolderComposite)file);
}
private void putToFileMoveSources(FileLeaf file) {
final IFileTree fileTree = action.getFileEventManager().getFileTree();
SetMultimap<String, FileComponent> deletedFiles = fileTree.getDeletedByContentHash();
deletedFiles.put(file.getContentHash(), file);
logger.debug("Put deleted file {} with hash {} to SetMultimap<String, FileComponent>", file.getPath(), file.getContentHash());
}
private AbstractActionState performSwappedMove(
FileComponent moveTarget) {
final IFileEventManager eventManager = action.getFileEventManager();
logger.trace("We observed a swapped folder move (deletion of source file "
+ "was reported after creation of target file: {} -> {}", action.getFile().getPath(), moveTarget.getPath());
eventManager.getFileTree().deleteFile(action.getFile().getPath());
eventManager.getFileComponentQueue().remove(moveTarget);
return handleLocalMove(moveTarget.getPath());
}
protected void logStateTransition(StateType stateBefore, EventType event, StateType stateAfter){
logger.debug("STATE_TRANSITION for file {}: {} + {} --> {}", action.getFile().getPath(),
stateBefore.getName(), event.getString(), stateAfter.getName());
}
}