diff --git a/core/src/saros/activities/DeletionAcknowledgmentActivity.java b/core/src/saros/activities/DeletionAcknowledgmentActivity.java new file mode 100644 index 0000000000..f51fe3137d --- /dev/null +++ b/core/src/saros/activities/DeletionAcknowledgmentActivity.java @@ -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(); + } +} diff --git a/core/src/saros/activities/FolderDeletedActivity.java b/core/src/saros/activities/FolderDeletedActivity.java index b11a2fb51c..b7f6f6a668 100644 --- a/core/src/saros/activities/FolderDeletedActivity.java +++ b/core/src/saros/activities/FolderDeletedActivity.java @@ -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. + * + *

NOTE: 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 { diff --git a/core/src/saros/activities/IActivityReceiver.java b/core/src/saros/activities/IActivityReceiver.java index dd57bc5dd9..1fa6978ac6 100644 --- a/core/src/saros/activities/IActivityReceiver.java +++ b/core/src/saros/activities/IActivityReceiver.java @@ -31,6 +31,10 @@ default void receive(ChecksumErrorActivity checksumErrorActivity) { /*NOP*/ } + default void receive(DeletionAcknowledgmentActivity deletionAcknowledgmentActivity) { + /*NOP*/ + } + default void receive(EditorActivity editorActivity) { /*NOP*/ } diff --git a/core/src/saros/communication/extensions/ActivitiesExtension.java b/core/src/saros/communication/extensions/ActivitiesExtension.java index 65d7e60bcc..7ca1986451 100644 --- a/core/src/saros/communication/extensions/ActivitiesExtension.java +++ b/core/src/saros/communication/extensions/ActivitiesExtension.java @@ -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; @@ -143,6 +144,7 @@ private Provider() { ChangeColorActivity.class, ChecksumActivity.class, ChecksumErrorActivity.class, + DeletionAcknowledgmentActivity.class, EditorActivity.class, FileActivity.class, FolderCreatedActivity.class, diff --git a/core/src/saros/concurrent/jupiter/internal/Jupiter.java b/core/src/saros/concurrent/jupiter/internal/Jupiter.java index 81922d4069..b36e252882 100644 --- a/core/src/saros/concurrent/jupiter/internal/Jupiter.java +++ b/core/src/saros/concurrent/jupiter/internal/Jupiter.java @@ -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); } } diff --git a/core/src/saros/concurrent/management/ConcurrentDocumentClient.java b/core/src/saros/concurrent/management/ConcurrentDocumentClient.java index 6b57e96aac..c440e91e3f 100644 --- a/core/src/saros/concurrent/management/ConcurrentDocumentClient.java +++ b/core/src/saros/concurrent/management/ConcurrentDocumentClient.java @@ -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; /** @@ -24,7 +23,7 @@ *

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); @@ -32,10 +31,23 @@ public class ConcurrentDocumentClient { 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(); } /** @@ -68,6 +80,9 @@ public IActivity transformToJupiter(IActivity activity) { return jupiterClient.withTimestamp(checksumActivity); } else { + resourceActivityFilter.handleFileDeletion(activity); + resourceActivityFilter.handleFileCreation(activity); + return activity; } } @@ -78,6 +93,9 @@ public IActivity transformToJupiter(IActivity activity) { *

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 * + *

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 @@ -91,7 +109,13 @@ public List transformFromJupiter(IActivity activity) { List activities = new ArrayList(); 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)); @@ -102,6 +126,8 @@ public List transformFromJupiter(IActivity activity) { activities.add(activity); } + resourceActivityFilter.handleFileDeletion(activity); + } catch (Exception e) { log.error("Error while transforming activity: " + activity, e); } @@ -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. * @@ -174,7 +189,7 @@ private List 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); } diff --git a/core/src/saros/concurrent/management/ConcurrentDocumentServer.java b/core/src/saros/concurrent/management/ConcurrentDocumentServer.java index 282e29e7c0..6efb89af5a 100644 --- a/core/src/saros/concurrent/management/ConcurrentDocumentServer.java +++ b/core/src/saros/concurrent/management/ConcurrentDocumentServer.java @@ -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; @@ -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() { @@ -53,42 +54,46 @@ public void userLeft(final User user) { public ConcurrentDocumentServer(final ISarosSession sarosSession) { this.sarosSession = sarosSession; this.server = new JupiterServer(sarosSession); + + Consumer 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. * + *

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!! @@ -104,9 +109,13 @@ public List transformIncoming(final IActivity activity) { final List result = new ArrayList(); - 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)); diff --git a/core/src/saros/concurrent/management/ResourceActivityFilter.java b/core/src/saros/concurrent/management/ResourceActivityFilter.java new file mode 100644 index 0000000000..ae3f927691 --- /dev/null +++ b/core/src/saros/concurrent/management/ResourceActivityFilter.java @@ -0,0 +1,297 @@ +package saros.concurrent.management; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import org.apache.log4j.Logger; +import saros.activities.ChecksumActivity; +import saros.activities.DeletionAcknowledgmentActivity; +import saros.activities.EditorActivity; +import saros.activities.FileActivity; +import saros.activities.IActivity; +import saros.activities.IResourceActivity; +import saros.activities.SPath; +import saros.filesystem.IProject; +import saros.session.AbstractActivityConsumer; +import saros.session.IActivityConsumer; +import saros.session.IActivityConsumer.Priority; +import saros.session.ISarosSession; +import saros.session.ISessionListener; +import saros.session.User; + +/** Class to handle file deletions and filter out resource activities for already deleted files. */ +class ResourceActivityFilter { + private static final Logger log = Logger.getLogger(ResourceActivityFilter.class); + + private final ISarosSession sarosSession; + + /** Method passed by the constructor caller that can be used to react to file deletions. */ + private final Consumer fileDeletionHandler; + + /** + * A map of shared files that were deleted during the session onto the pending acknowledgments + * from other participants. It is used to filter out activities for such files that were created + * before the other participants ran the corresponding file deletion activity locally. + * + *

The set is updated once the file is recreated as we then want to handle activities for it + * again. Furthermore, it is updated once all acknowledgments for a file deletion were received as + * the filter is then no longer necessary. + * + *

This way of filtering can lead to issues when the order of activities is not preserved, e.g. + * when we receive the content change for a new file is received before the file creation. Such + * activities will be dropped, leading to inconsistencies. + */ + private final Map> deletedFileFilter; + + /** Activity consumer processing received deletion acknowledgments. */ + private final IActivityConsumer activityConsumer = + new AbstractActivityConsumer() { + @Override + public void receive(DeletionAcknowledgmentActivity deletionAcknowledgmentActivity) { + User source = deletionAcknowledgmentActivity.getSource(); + SPath file = deletionAcknowledgmentActivity.getPath(); + + List remainingUsers = deletedFileFilter.get(file); + + if (remainingUsers == null) { + log.warn( + "Received unexpected deletion acknowledgment for file that is not filtered: " + + source + + " - " + + file); + + return; + + } else if (!remainingUsers.contains(source)) { + log.warn( + "Received acknowledgment for file deletion from unexpected user: " + + source + + " - " + + file); + + return; + } + + log.debug("Received deletion acknowledgment from " + source + " for " + file); + + remainingUsers.remove(source); + + if (remainingUsers.isEmpty()) { + log.debug( + "Dropping activity filter for " + file + " as all acknowledgments were received"); + + deletedFileFilter.remove(file); + } + } + }; + + /** + * Session listener updating the held map of filtered files when participants leave the session or + * project are removed from the session. + */ + private final ISessionListener sessionListener = + new ISessionListener() { + @Override + public void userLeft(User user) { + Iterator>> iterator = deletedFileFilter.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + + List remainingUsers = entry.getValue(); + remainingUsers.remove(user); + + if (remainingUsers.isEmpty()) { + log.debug( + "Dropping activity filter for " + + entry.getKey() + + " as there are no more pending acknowledgments"); + + iterator.remove(); + } + } + } + + @Override + public void projectRemoved(IProject project) { + Iterator>> iterator = deletedFileFilter.entrySet().iterator(); + while (iterator.hasNext()) { + SPath file = iterator.next().getKey(); + + if (file.getProject().equals(project)) { + log.debug( + "Dropping activity filter for " + + file + + " as it is no longer part of the session"); + + iterator.remove(); + } + } + } + }; + + /** + * Creates a new deleted file filter. The passed method is called every time a file deletion is + * detected after the file is added to the map of deleted file. + * + * @param sarosSession the current saros session + * @param fileDeletionHandler method that is called every time a file deletion is detected + */ + ResourceActivityFilter(ISarosSession sarosSession, Consumer fileDeletionHandler) { + this.sarosSession = sarosSession; + this.fileDeletionHandler = fileDeletionHandler; + + this.deletedFileFilter = new ConcurrentHashMap<>(); + } + + /** + * Adds the deleted file to the held map of deleted shared files. This causes activities for such + * files to be detected as filtered out by {@link #isFiltered(IActivity)} until it is created + * again (or all deletion acknowledgments were received). Subsequently calls {@link + * #fileDeletionHandler} with the deleted file. + * + *

Does nothing if the passed activity is not a {@link FileActivity} or does not have the type + * {@link FileActivity.Type#REMOVED} or {@link FileActivity.Type#MOVED}. + * + * @param activity the activity to handle + * @see #deletedFileFilter + * @see #handleFileCreation(IActivity) + */ + void handleFileDeletion(IActivity activity) { + if (!(activity instanceof FileActivity)) { + return; + } + + FileActivity fileActivity = (FileActivity) activity; + + SPath removedFile; + if (fileActivity.getType() == FileActivity.Type.REMOVED) { + removedFile = fileActivity.getPath(); + + } else if (fileActivity.getType() == FileActivity.Type.MOVED + && !fileActivity.getPath().equals(fileActivity.getOldPath())) { + + removedFile = fileActivity.getOldPath(); + + } else { + return; + } + + List remoteUsers = sarosSession.getRemoteUsers(); + + remoteUsers.remove(activity.getSource()); + + if (!remoteUsers.isEmpty()) { + log.debug( + "Adding activity filter for deleted file " + + removedFile + + ", waiting for acknowledgment from user(s) " + + remoteUsers); + + deletedFileFilter.put(removedFile, remoteUsers); + } + + fileDeletionHandler.accept(removedFile); + } + + /** + * Removes the created file from the held map of deleted shared files. This causes activities for + * the files to no longer be detected as filtered out by {@link #isFiltered(IActivity)}. + * + *

Does nothing if the passed activity is not a {@link FileActivity} or does not have the type + * {@link FileActivity.Type#CREATED} or {@link FileActivity.Type#MOVED}. + * + * @param activity the activity to handle + * @see #deletedFileFilter + * @see #handleFileDeletion(IActivity) + */ + void handleFileCreation(IActivity activity) { + if (!(activity instanceof FileActivity)) { + return; + } + + FileActivity fileActivity = (FileActivity) activity; + + if (fileActivity.getType() == FileActivity.Type.MOVED + || fileActivity.getType() == FileActivity.Type.CREATED) { + + SPath addedFile = fileActivity.getPath(); + + if (deletedFileFilter.containsKey(addedFile)) { + log.debug("Removing activity filter for re-created file " + addedFile); + + deletedFileFilter.remove(addedFile); + } + } + } + + /** + * Returns whether the passed activity is filtered out. This is determined by the held map of + * deleted files. + * + *

Non-resource activities are never detected as being filtered. Furthermore, resource + * activities dealing with the tear-down of the internal Saros state related to the deleted + * resource are never detected as being filtered. This applies to the following kinds of + * activities: + * + *

+ * + * @param activity the activity to check + * @return whether the passed activity is filtered out + * @see #handleFileDeletion(IActivity) + * @see #handleFileCreation(IActivity) + */ + boolean isFiltered(IActivity activity) { + + if (!(activity instanceof IResourceActivity) + || activity instanceof DeletionAcknowledgmentActivity) { + + return false; + } + + SPath path = ((IResourceActivity) activity).getPath(); + + if (path == null) { + return false; + } + + boolean pathIsFiltered = deletedFileFilter.containsKey(path); + + if (pathIsFiltered) { + if (activity instanceof ChecksumActivity) { + ChecksumActivity checksumActivity = (ChecksumActivity) activity; + + return checksumActivity.getHash() != ChecksumActivity.NON_EXISTING_DOC + && checksumActivity.getLength() != ChecksumActivity.NON_EXISTING_DOC; + + } else if (activity instanceof EditorActivity) { + EditorActivity editorActivity = (EditorActivity) activity; + + return EditorActivity.Type.CLOSED != editorActivity.getType(); + } + } + + return pathIsFiltered; + } + + /** Initializes all contained components. */ + public void initialize() { + sarosSession.addActivityConsumer(activityConsumer, Priority.PASSIVE); + sarosSession.addListener(sessionListener); + } + + /** Disposes all contained components to prepare them for garbage collection. */ + public void dispose() { + sarosSession.removeActivityConsumer(activityConsumer); + sarosSession.removeListener(sessionListener); + } +} diff --git a/core/src/saros/session/SarosCoreSessionContextFactory.java b/core/src/saros/session/SarosCoreSessionContextFactory.java index ae9aab4ac3..7dfa819e3a 100644 --- a/core/src/saros/session/SarosCoreSessionContextFactory.java +++ b/core/src/saros/session/SarosCoreSessionContextFactory.java @@ -15,6 +15,7 @@ import saros.session.internal.ActivityHandler; import saros.session.internal.ActivitySequencer; import saros.session.internal.ChangeColorManager; +import saros.session.internal.DeletionAcknowledgmentDispatcher; import saros.session.internal.LeaveAndKickHandler; import saros.session.internal.PermissionManager; import saros.session.internal.UserInformationHandler; @@ -58,6 +59,7 @@ public final void createComponents(ISarosSession session, MutablePicoContainer c container.addComponent(ActivityHandler.class); container.addComponent(ActivitySequencer.class); container.addComponent(ChangeColorManager.class); + container.addComponent(DeletionAcknowledgmentDispatcher.class); container.addComponent(FollowModeManager.class); container.addComponent(FollowModeBroadcaster.class); container.addComponent(LeaveAndKickHandler.class); diff --git a/core/src/saros/session/internal/ActivityHandler.java b/core/src/saros/session/internal/ActivityHandler.java index 384d629632..ff971037cd 100644 --- a/core/src/saros/session/internal/ActivityHandler.java +++ b/core/src/saros/session/internal/ActivityHandler.java @@ -401,7 +401,7 @@ private TransformationResult directServerActivities(List activities) final List allUsers = session.getUsers(); for (IActivity activity : activities) { - documentServer.checkFileDeleted(activity); + documentServer.handleResourceChange(activity); if (activity instanceof JupiterActivity || activity instanceof ChecksumActivity) { diff --git a/core/src/saros/session/internal/DeletionAcknowledgmentDispatcher.java b/core/src/saros/session/internal/DeletionAcknowledgmentDispatcher.java new file mode 100644 index 0000000000..96ec3c9883 --- /dev/null +++ b/core/src/saros/session/internal/DeletionAcknowledgmentDispatcher.java @@ -0,0 +1,84 @@ +package saros.session.internal; + +import org.apache.log4j.Logger; +import saros.activities.DeletionAcknowledgmentActivity; +import saros.activities.FileActivity; +import saros.activities.FileActivity.Type; +import saros.activities.IActivity; +import saros.activities.SPath; +import saros.repackaged.picocontainer.Startable; +import saros.session.AbstractActivityConsumer; +import saros.session.AbstractActivityProducer; +import saros.session.IActivityConsumer; +import saros.session.IActivityConsumer.Priority; +import saros.session.ISarosSession; +import saros.session.User; + +/** + * Class reacting to the reception of activities containing the deletion of a file by sending an + * acknowledgement. + * + * @see DeletionAcknowledgmentActivity + */ +public class DeletionAcknowledgmentDispatcher extends AbstractActivityProducer + implements Startable { + + private static final Logger log = Logger.getLogger(DeletionAcknowledgmentDispatcher.class); + + private final ISarosSession sarosSession; + + private final IActivityConsumer activityConsumer = + new AbstractActivityConsumer() { + @Override + public void receive(FileActivity fileActivity) { + acknowledgeDeletionActivity(fileActivity); + } + }; + + public DeletionAcknowledgmentDispatcher(ISarosSession sarosSession) { + this.sarosSession = sarosSession; + } + + @Override + public void start() { + sarosSession.addActivityProducer(this); + sarosSession.addActivityConsumer(activityConsumer, Priority.PASSIVE); + } + + @Override + public void stop() { + sarosSession.removeActivityProducer(this); + sarosSession.removeActivityConsumer(activityConsumer); + } + + /** + * Checks whether the passed file activity contains the deletion of a file and sends an + * acknowledgment to all other participants. + * + *

Activities that contain file deletions are {@link FileActivity file activities} of the type + * {@link Type#REMOVED} or {@link Type#MOVED}. + * + * @param fileActivity the file activity to check and acknowledge if applicable + */ + private void acknowledgeDeletionActivity(FileActivity fileActivity) { + SPath deletedResource; + + if (fileActivity.getType() == Type.MOVED) { + deletedResource = fileActivity.getOldPath(); + + } else if (fileActivity.getType() == Type.REMOVED) { + deletedResource = fileActivity.getPath(); + + } else { + return; + } + + User localUser = sarosSession.getLocalUser(); + + IActivity deletionAcknowledgementActivity = + new DeletionAcknowledgmentActivity(localUser, deletedResource); + + log.debug("Sending deletion acknowledgment for " + deletedResource); + fireActivity(deletionAcknowledgementActivity); + } +} diff --git a/intellij/src/saros/intellij/eventhandler/filesystem/LocalFilesystemModificationHandler.java b/intellij/src/saros/intellij/eventhandler/filesystem/LocalFilesystemModificationHandler.java index 20f7438d0c..42bcd57a4f 100644 --- a/intellij/src/saros/intellij/eventhandler/filesystem/LocalFilesystemModificationHandler.java +++ b/intellij/src/saros/intellij/eventhandler/filesystem/LocalFilesystemModificationHandler.java @@ -475,8 +475,6 @@ private void generateFileDeletionActivity(VirtualFile deletedFile) { cleanUpDeletedFileState(path); dispatchActivity(activity); - - // TODO reset the vector time for the deleted file or contained files if folder } /** @@ -835,15 +833,11 @@ private void generateFileMoveActivity( dispatchActivity(activity); - if (oldPathIsShared) { - if (fileIsOpen) { - EditorActivity closeOldEditorActivity = - new EditorActivity(user, EditorActivity.Type.CLOSED, oldFilePath); - - dispatchActivity(closeOldEditorActivity); - } + if (oldPathIsShared && fileIsOpen) { + EditorActivity closeOldEditorActivity = + new EditorActivity(user, EditorActivity.Type.CLOSED, oldFilePath); - // TODO reset the vector time for the old file + dispatchActivity(closeOldEditorActivity); } if (newPathIsShared && fileIsOpen) {