Skip to content
28 changes: 28 additions & 0 deletions core/src/saros/activities/DeletionAcknowledgmentActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package saros.activities;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import saros.session.User;
import saros.session.internal.DeletionAcknowledgmentDispatcher;

/**
* Activity for notifying other participants that a file was successfully deleted locally.
*
* @see DeletionAcknowledgmentDispatcher
*/
@XStreamAlias("deletionAcknowledgementActivity")
public class DeletionAcknowledgmentActivity extends AbstractResourceActivity {

public DeletionAcknowledgmentActivity(User user, SPath resource) {
super(user, resource);
}

@Override
public void dispatch(IActivityReceiver receiver) {
receiver.receive(this);
}

@Override
public String toString() {
return this.getClass().getSimpleName() + " : " + getSource() + " - " + getPath();
}
}
12 changes: 11 additions & 1 deletion core/src/saros/activities/FolderDeletedActivity.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package saros.activities;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import saros.concurrent.management.ConcurrentDocumentClient;
import saros.concurrent.management.ConcurrentDocumentServer;
import saros.session.User;

/** An activity that represents the deletion of a folder made by a user during a session. */
/**
* An activity that represents the deletion of a folder made by a user during a session.
*
* <p><b>NOTE:</b> Any resource that is contained in the deleted folder should have been processed
* separately before dispatching the folder deletion activity. This is important to allow the other
* session participants to clean up the state of all deleted child resources. Furthermore, the
* explicit handling of deleted child resources is required by the {@link ConcurrentDocumentServer}
* and {@link ConcurrentDocumentClient}.
*/
@XStreamAlias("folderDeleted")
public class FolderDeletedActivity extends AbstractResourceActivity
implements IFileSystemModificationActivity {
Expand Down
4 changes: 4 additions & 0 deletions core/src/saros/activities/IActivityReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ default void receive(ChecksumErrorActivity checksumErrorActivity) {
/*NOP*/
}

default void receive(DeletionAcknowledgmentActivity deletionAcknowledgmentActivity) {
/*NOP*/
}

default void receive(EditorActivity editorActivity) {
/*NOP*/
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import saros.activities.ChangeColorActivity;
import saros.activities.ChecksumActivity;
import saros.activities.ChecksumErrorActivity;
import saros.activities.DeletionAcknowledgmentActivity;
import saros.activities.EditorActivity;
import saros.activities.FileActivity;
import saros.activities.FolderCreatedActivity;
Expand Down Expand Up @@ -143,6 +144,7 @@ private Provider() {
ChangeColorActivity.class,
ChecksumActivity.class,
ChecksumErrorActivity.class,
DeletionAcknowledgmentActivity.class,
EditorActivity.class,
FileActivity.class,
FolderCreatedActivity.class,
Expand Down
12 changes: 8 additions & 4 deletions core/src/saros/concurrent/jupiter/internal/Jupiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,20 @@ protected void checkPreconditions(JupiterVectorTime time) throws TransformationE
if (!this.ackJupiterActivityList.isEmpty()
&& (time.getRemoteOperationCount()
< this.ackJupiterActivityList.get(0).getLocalOperationCount())) {
// TODO improve exception message; what is precondition 1?
throw new TransformationException("Precondition #1 violated.");
} else if (time.getRemoteOperationCount() > this.vectorTime.getLocalOperationCount()) {
throw new TransformationException(
"precondition #2 violated (Remote vector time is greater than local vector time).");
"precondition #2 violated (Remote vector time is greater than local vector time) - remote time: "
+ time
+ " ,local time: "
+ vectorTime);
} else if (time.getLocalOperationCount() != this.vectorTime.getRemoteOperationCount()) {
throw new TransformationException(
"Precondition #3 violated (Vector time does not match): "
"Precondition #3 violated (Vector time does not match) - remote time :"
+ time
+ " , "
+ this.vectorTime);
+ " ,local time: "
+ vectorTime);
}
}

Expand Down
49 changes: 32 additions & 17 deletions core/src/saros/concurrent/management/ConcurrentDocumentClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
import java.util.List;
import org.apache.log4j.Logger;
import saros.activities.ChecksumActivity;
import saros.activities.FileActivity;
import saros.activities.IActivity;
import saros.activities.IActivityReceiver;
import saros.activities.JupiterActivity;
import saros.activities.SPath;
import saros.activities.TextEditActivity;
import saros.concurrent.jupiter.Operation;
import saros.concurrent.jupiter.TransformationException;
import saros.repackaged.picocontainer.Startable;
import saros.session.ISarosSession;

/**
Expand All @@ -24,18 +23,31 @@
* <p>When JupiterActivities are received from the server they are transformed by the
* ConcurrentDocumentClient to TextEditActivities which can then be executed locally.
*/
public class ConcurrentDocumentClient {
public class ConcurrentDocumentClient implements Startable {

private static Logger log = Logger.getLogger(ConcurrentDocumentClient.class);

private final ISarosSession sarosSession;

private final JupiterClient jupiterClient;

public ConcurrentDocumentClient(ISarosSession sarosSession) {
private final ResourceActivityFilter resourceActivityFilter;

public ConcurrentDocumentClient(ISarosSession sarosSession) {
this.sarosSession = sarosSession;
this.jupiterClient = new JupiterClient(sarosSession);

this.resourceActivityFilter = new ResourceActivityFilter(sarosSession, this::reset);
}

@Override
public void start() {
resourceActivityFilter.initialize();
}

@Override
public void stop() {
resourceActivityFilter.dispose();
}

/**
Expand Down Expand Up @@ -68,6 +80,9 @@ public IActivity transformToJupiter(IActivity activity) {
return jupiterClient.withTimestamp(checksumActivity);

} else {
resourceActivityFilter.handleFileDeletion(activity);
resourceActivityFilter.handleFileCreation(activity);

return activity;
}
}
Expand All @@ -78,6 +93,9 @@ public IActivity transformToJupiter(IActivity activity) {
* <p>This method will transform them back from Jupiter-specific activities to locally executable
* activities. @GUI Must be called on the GUI Thread to ensure proper synchronization
*
* <p>Drops activities that are reported as filtered out by {@link
* ResourceActivityFilter#isFiltered(IActivity)}.
*
* @host and @client This is called whenever activities are received from REMOTELY both on the
* client and on the host
* @param activity The activity to be transformed
Expand All @@ -91,7 +109,13 @@ public List<IActivity> transformFromJupiter(IActivity activity) {
List<IActivity> activities = new ArrayList<IActivity>();

try {
activity.dispatch(clientReceiver);
resourceActivityFilter.handleFileCreation(activity);

if (resourceActivityFilter.isFiltered(activity)) {
log.debug("Ignored activity for already deleted resource: " + activity);

return activities;
}

if (activity instanceof JupiterActivity) {
activities.addAll(receiveActivity((JupiterActivity) activity));
Expand All @@ -102,6 +126,8 @@ public List<IActivity> transformFromJupiter(IActivity activity) {
activities.add(activity);
}

resourceActivityFilter.handleFileDeletion(activity);

} catch (Exception e) {
log.error("Error while transforming activity: " + activity, e);
}
Expand All @@ -123,17 +149,6 @@ private IActivity receiveChecksum(ChecksumActivity activity) {
return activity;
}

/** Used to remove JupiterClientDocuments for deleted files */
private final IActivityReceiver clientReceiver =
new IActivityReceiver() {
@Override
public void receive(FileActivity fileActivity) {
if (fileActivity.getType() == FileActivity.Type.REMOVED) {
jupiterClient.reset(fileActivity.getPath());
}
}
};

/**
* Transforms the JupiterActivity back into textEditActivities.
*
Expand Down Expand Up @@ -174,7 +189,7 @@ private List<IActivity> receiveActivity(JupiterActivity jupiterActivity) {
* authoritative one and thus does not need to be reset).
*/
public synchronized void reset(SPath path) {
log.debug("Resetting jupiter client: " + path.toString());
log.debug("Resetting jupiter client for " + path);
jupiterClient.reset(path);
}

Expand Down
47 changes: 28 additions & 19 deletions core/src/saros/concurrent/management/ConcurrentDocumentServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.apache.log4j.Logger;
import saros.activities.ChecksumActivity;
import saros.activities.FileActivity;
import saros.activities.IActivity;
import saros.activities.IActivityReceiver;
import saros.activities.JupiterActivity;
import saros.activities.QueueItem;
import saros.activities.SPath;
Expand All @@ -35,6 +34,8 @@ public class ConcurrentDocumentServer implements Startable {

private final JupiterServer server;

private final ResourceActivityFilter resourceActivityFilter;

/** {@link ISessionListener} for updating Jupiter documents on the host. */
private final ISessionListener sessionListener =
new ISessionListener() {
Expand All @@ -53,42 +54,46 @@ public void userLeft(final User user) {
public ConcurrentDocumentServer(final ISarosSession sarosSession) {
this.sarosSession = sarosSession;
this.server = new JupiterServer(sarosSession);

Consumer<SPath> deletedFileHandler =
resource -> {
LOG.debug("Resetting jupiter server for " + resource);
server.removePath(resource);
};

this.resourceActivityFilter = new ResourceActivityFilter(sarosSession, deletedFileHandler);
}

@Override
public void start() {
sarosSession.addListener(sessionListener);
resourceActivityFilter.initialize();
}

@Override
public void stop() {
sarosSession.removeListener(sessionListener);
resourceActivityFilter.dispose();
}

/**
* Dispatched the activity to the internal ActivityReceiver. The ActivityReceiver will remove
* FileDocuments when the file has been deleted.
* Calls {@link ResourceActivityFilter#handleFileDeletion(IActivity)} and {@link
* ResourceActivityFilter#handleFileCreation(IActivity)} with the given activity.
*
* @param activity Activity to be dispatched
* @param activity the activity to handle
*/
public void checkFileDeleted(final IActivity activity) {
activity.dispatch(hostReceiver);
public void handleResourceChange(IActivity activity) {
resourceActivityFilter.handleFileDeletion(activity);
resourceActivityFilter.handleFileCreation(activity);
}

private final IActivityReceiver hostReceiver =
new IActivityReceiver() {
@Override
public void receive(final FileActivity activity) {
if (activity.getType() == FileActivity.Type.REMOVED) {
server.removePath(activity.getPath());
}
}
};

/**
* Transforms the given activities on the server side and returns a list of QueueItems containing
* the transformed activities and there receivers.
*
* <p>Drops activities that are reported as filtered out by {@link
* ResourceActivityFilter#isFiltered(IActivity)}.
*
* @host
* @sarosThread Must be executed in the Saros dispatch thread.
* @notGUI This method may not be called from SWT, otherwise a deadlock might occur!!
Expand All @@ -104,9 +109,13 @@ public List<QueueItem> transformIncoming(final IActivity activity) {

final List<QueueItem> result = new ArrayList<QueueItem>();

try {
activity.dispatch(hostReceiver);
if (resourceActivityFilter.isFiltered(activity)) {
LOG.debug("Ignored activity for already deleted resource: " + activity);

return result;
}

try {
if (activity instanceof JupiterActivity) {
result.addAll(receive((JupiterActivity) activity));

Expand Down
Loading