Fix sync issues (WIP)
This commit is contained in:
parent
1a120f98eb
commit
856171acff
36
eclipse.target
Normal file
36
eclipse.target
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?pde version="3.8"?>
|
||||
<target name="Running Platform">
|
||||
<locations>
|
||||
<location path="${eclipse_home}" type="Profile"/>
|
||||
<location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="generate" type="Maven">
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>me.mrletsplay</groupId>
|
||||
<artifactId>ShareLib</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</location>
|
||||
<location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="generate" type="Maven">
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>1.5.6</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</location>
|
||||
</locations>
|
||||
<environment>
|
||||
<arch>x86_64</arch>
|
||||
<os>linux</os>
|
||||
<ws>gtk</ws>
|
||||
<nl>en_US</nl>
|
||||
</environment>
|
||||
<launcherArgs>
|
||||
<vmArgs>-Declipse.p2.max.threads=10 -Doomph.update.url=https://download.eclipse.org/oomph/updates/milestone/latest -Doomph.redirection.index.redirection=index:/->http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/ -Dosgi.requiredJavaVersion=17 -Dosgi.instance.area.default=@user.home/eclipse-workspace -Dosgi.dataAreaRequiresExplicitInit=true -Dorg.eclipse.swt.graphics.Resource.reportNonDisposed=true -Dsun.java.command=Eclipse -XX:+UseG1GC -XX:+UseStringDeduplication --add-modules=ALL-SYSTEM -Dorg.eclipse.ecf.provider.filetransfer.excludeContributors=org.eclipse.ecf.provider.filetransfer.httpclientjava -Dosgi.requiredJavaVersion=17 -Dosgi.instance.area.default=@user.home/eclipse-workspace -Dosgi.dataAreaRequiresExplicitInit=true -Dorg.eclipse.swt.graphics.Resource.reportNonDisposed=true -Declipse.e4.inject.javax.warning=false -Dorg.slf4j.simpleLogger.defaultLogLevel=off -Dsun.java.command=Eclipse -Xms256m -Xmx2048m -XX:+UseG1GC -XX:+UseStringDeduplication --add-modules=ALL-SYSTEM -Djava.security.manager=allow</vmArgs>
|
||||
</launcherArgs>
|
||||
</target>
|
@ -30,6 +30,7 @@ import me.mrletsplay.shareclient.util.ChecksumUtil;
|
||||
import me.mrletsplay.shareclient.util.Peer;
|
||||
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
import me.mrletsplay.shareclient.util.SharedProject;
|
||||
import me.mrletsplay.shareclient.util.listeners.ShareClientDocumentListener;
|
||||
import me.mrletsplay.shareclient.util.listeners.ShareClientPageListener;
|
||||
import me.mrletsplay.shareclient.util.listeners.ShareClientPartListener;
|
||||
@ -37,6 +38,7 @@ import me.mrletsplay.shareclient.util.listeners.ShareClientWindowListener;
|
||||
import me.mrletsplay.shareclient.views.ShareView;
|
||||
import me.mrletsplay.shareclientcore.connection.Change;
|
||||
import me.mrletsplay.shareclientcore.connection.ConnectionException;
|
||||
import me.mrletsplay.shareclientcore.connection.DisconnectListener;
|
||||
import me.mrletsplay.shareclientcore.connection.MessageListener;
|
||||
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
||||
import me.mrletsplay.shareclientcore.connection.WebSocketConnection;
|
||||
@ -53,7 +55,7 @@ import me.mrletsplay.shareclientcore.document.SharedDocument;
|
||||
/**
|
||||
* The activator class controls the plug-in life cycle
|
||||
*/
|
||||
public class ShareClient extends AbstractUIPlugin implements MessageListener, IStartup {
|
||||
public class ShareClient extends AbstractUIPlugin implements MessageListener, DisconnectListener, IStartup {
|
||||
|
||||
// The plug-in ID
|
||||
public static final String PLUGIN_ID = "ShareClient"; //$NON-NLS-1$
|
||||
@ -132,6 +134,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
}
|
||||
|
||||
connection.addListener(this);
|
||||
connection.setDisconnectListener(this);
|
||||
|
||||
updateView();
|
||||
return activeSession = new ShareSession(connection, sessionID);
|
||||
@ -180,7 +183,6 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
}
|
||||
|
||||
if (message instanceof FullSyncMessage sync) {
|
||||
// TODO: handle FULL_SYNC
|
||||
ProjectRelativePath path;
|
||||
try {
|
||||
path = ProjectRelativePath.of(sync.documentPath());
|
||||
@ -189,27 +191,36 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
return;
|
||||
}
|
||||
|
||||
ShareClientDocumentListener listener = partListener.getListeners().get(path);
|
||||
ShareClientDocumentListener listener = partListener.getListener(path);
|
||||
|
||||
IWorkspace workspace = ResourcesPlugin.getWorkspace();
|
||||
IWorkspaceRoot workspaceRoot = workspace.getRoot();
|
||||
IProject project = workspaceRoot.getProject(path.projectName());
|
||||
if(project == null) return;
|
||||
// TODO: make sure to not overwrite existing non-shared projects
|
||||
if (!project.exists()) {
|
||||
IProjectDescription description = workspace.newProjectDescription(path.projectName());
|
||||
try {
|
||||
project.create(description, null);
|
||||
project.open(null);
|
||||
} catch (CoreException e) {
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to create project: " + e.toString());
|
||||
return;
|
||||
SharedProject sharedProject = activeSession.getSharedProject(path.projectName());
|
||||
if(sharedProject == null) {
|
||||
// New project, TODO: prompt user to choose location
|
||||
IProject project = workspaceRoot.getProject(path.projectName());
|
||||
if(project == null) return;
|
||||
|
||||
// TODO: make sure to not overwrite existing non-shared projects
|
||||
if (!project.exists()) {
|
||||
IProjectDescription description = workspace.newProjectDescription(path.projectName());
|
||||
try {
|
||||
project.create(description, null);
|
||||
project.open(null);
|
||||
} catch (CoreException e) {
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to create project: " + e.toString());
|
||||
return;
|
||||
}
|
||||
System.out.println("Created project: " + project);
|
||||
}
|
||||
System.out.println("Created project " + project);
|
||||
|
||||
sharedProject = new SharedProject(path.projectName(), project);
|
||||
activeSession.getSharedProjects().add(sharedProject);
|
||||
System.out.println("Received new project: " + path.projectName());
|
||||
}
|
||||
|
||||
Path filePath = project.getLocation().toPath().resolve(path.relativePath());
|
||||
Path filePath = sharedProject.getLocal().getLocation().toPath().resolve(path.relativePath());
|
||||
try {
|
||||
if (!Files.exists(filePath)) {
|
||||
Files.createDirectories(filePath.getParent());
|
||||
@ -218,10 +229,10 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
|
||||
// TODO: update sharedDocuments
|
||||
|
||||
listener.setIgnoreChanges(true);
|
||||
if(listener != null) listener.setIgnoreChanges(true);
|
||||
Files.write(filePath, sync.content());
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, null);
|
||||
listener.setIgnoreChanges(false);
|
||||
sharedProject.getLocal().refreshLocal(IResource.DEPTH_INFINITE, null);
|
||||
if(listener != null) listener.setIgnoreChanges(false);
|
||||
} catch (IOException | CoreException e) {
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to update file: " + e.toString());
|
||||
@ -233,7 +244,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
Map<ProjectRelativePath, Path> paths = new HashMap<>();
|
||||
if (req.documentPath() == null) {
|
||||
// Sync entire (shared) workspace
|
||||
for (IProject project : activeSession.getSharedProjects()) {
|
||||
for (SharedProject project : activeSession.getSharedProjects()) {
|
||||
var files = getProjectFiles(project);
|
||||
System.out.println(files);
|
||||
if (files == null) return;
|
||||
@ -247,18 +258,17 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
return;
|
||||
}
|
||||
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.projectName());
|
||||
if (project == null || !project.exists()) return;
|
||||
if (!activeSession.getSharedProjects().contains(project)) return;
|
||||
SharedProject sharedProject = activeSession.getSharedProject(path.projectName());
|
||||
if (sharedProject == null) return;
|
||||
|
||||
if (!path.relativePath().isEmpty()) {
|
||||
Path projectLocation = project.getLocation().toPath();
|
||||
Path projectLocation = sharedProject.getLocal().getLocation().toPath();
|
||||
Path filePath = projectLocation.resolve(path.relativePath()).normalize();
|
||||
if (!filePath.startsWith(projectLocation)) return;
|
||||
paths.put(new ProjectRelativePath(project.getName(), path.relativePath()), filePath);
|
||||
paths.put(new ProjectRelativePath(sharedProject.getRemoteName(), path.relativePath()), filePath);
|
||||
} else {
|
||||
// Sync entire project
|
||||
var files = getProjectFiles(project);
|
||||
var files = getProjectFiles(sharedProject);
|
||||
if (files == null) return;
|
||||
paths.putAll(files);
|
||||
}
|
||||
@ -296,11 +306,21 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(String reason, boolean remote) {
|
||||
MessageDialog.openError(null, "Share Client", "Disconnected (remote = " + remote + "): " + reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares a project. Includes sending full syncs to other clients
|
||||
* @param project The project to share
|
||||
*/
|
||||
public void addSharedProject(IProject project) {
|
||||
activeSession.getSharedProjects().add(project);
|
||||
SharedProject shared = new SharedProject(activeSession.getFreeRemoteName(project.getName()), project);
|
||||
activeSession.getSharedProjects().add(shared);
|
||||
|
||||
RemoteConnection connection = activeSession.getConnection();
|
||||
for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(project).entrySet()) {
|
||||
for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(shared).entrySet()) {
|
||||
// TODO: add new document to sharedDocuments
|
||||
if(!sendFullSyncOrChecksum(connection, AddressableMessage.BROADCAST_SITE_ID, en.getKey(), en.getValue(), false)) return;
|
||||
}
|
||||
@ -327,21 +347,20 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, IS
|
||||
}
|
||||
|
||||
private Path resolvePath(ProjectRelativePath path) {
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.projectName());
|
||||
if (project == null || !project.exists()) return null;
|
||||
if (!activeSession.getSharedProjects().contains(project)) return null;
|
||||
Path projectLocation = project.getLocation().toPath();
|
||||
SharedProject sharedProject = activeSession.getSharedProject(path.projectName());
|
||||
if(sharedProject == null) return null;
|
||||
Path projectLocation = sharedProject.getLocal().getLocation().toPath();
|
||||
Path filePath = projectLocation.resolve(path.relativePath()).normalize();
|
||||
if (!filePath.startsWith(projectLocation)) return null;
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) {
|
||||
private Map<ProjectRelativePath, Path> getProjectFiles(SharedProject project) {
|
||||
try {
|
||||
Path projectLocation = project.getLocation().toPath();
|
||||
Path projectLocation = project.getLocal().getLocation().toPath();
|
||||
return Files.walk(projectLocation)
|
||||
.collect(Collectors.toMap(
|
||||
p -> new ProjectRelativePath(project.getName(), projectLocation.relativize(p).toString()),
|
||||
p -> new ProjectRelativePath(project.getRemoteName(), projectLocation.relativize(p).toString()),
|
||||
Function.identity()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -0,0 +1,5 @@
|
||||
package me.mrletsplay.shareclient.util;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
||||
public record ProjectAndPath(IProject project, String path) {}
|
@ -22,7 +22,7 @@ public class ShareSession {
|
||||
|
||||
private RemoteConnection connection;
|
||||
private String sessionID;
|
||||
private List<IProject> sharedProjects;
|
||||
private List<SharedProject> sharedProjects;
|
||||
private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
|
||||
private List<Peer> peers;
|
||||
|
||||
@ -42,10 +42,38 @@ public class ShareSession {
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
public List<IProject> getSharedProjects() {
|
||||
public List<SharedProject> getSharedProjects() {
|
||||
return sharedProjects;
|
||||
}
|
||||
|
||||
public SharedProject getSharedProject(String remoteName) {
|
||||
return sharedProjects.stream()
|
||||
.filter(p -> p.getRemoteName().equals(remoteName))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public SharedProject getSharedProject(IProject project) {
|
||||
return sharedProjects.stream()
|
||||
.filter(p -> p.getLocal().equals(project))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param projectName The name of the project
|
||||
* @return A remote name for the project that is not currently in use
|
||||
*/
|
||||
public String getFreeRemoteName(String projectName) {
|
||||
// TODO: technically, this can fail if two people try to share a project with the same name at the same time
|
||||
if(getSharedProject(projectName) == null) return projectName;
|
||||
|
||||
int n = 0;
|
||||
while(getSharedProject(projectName + n) != null) {
|
||||
n++;
|
||||
}
|
||||
|
||||
return projectName + n;
|
||||
}
|
||||
|
||||
public Map<ProjectRelativePath, SharedDocument> getSharedDocuments() {
|
||||
return sharedDocuments;
|
||||
}
|
||||
@ -63,8 +91,9 @@ public class ShareSession {
|
||||
|
||||
@Override
|
||||
public void onInsert(int index, char character) {
|
||||
// FIXME: potential desync. If changes arrive while waiting for asyncExec to execute, it will insert at the wrong position
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListeners().get(path);
|
||||
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
|
||||
if(documentListener != null) {
|
||||
IDocument document = documentListener.getDocument();
|
||||
|
||||
@ -84,7 +113,7 @@ public class ShareSession {
|
||||
@Override
|
||||
public void onDelete(int index) {
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListeners().get(path);
|
||||
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
|
||||
if(documentListener != null) {
|
||||
IDocument document = documentListener.getDocument();
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
package me.mrletsplay.shareclient.util;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
||||
/**
|
||||
* A project shared in a session. Contains a remote name (name used for {@link ProjectRelativePath}s) and a reference to the local project.
|
||||
*/
|
||||
public class SharedProject {
|
||||
|
||||
private String remoteName;
|
||||
private IProject local;
|
||||
|
||||
public SharedProject(String remoteName, IProject local) {
|
||||
super();
|
||||
this.remoteName = remoteName;
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
public String getRemoteName() {
|
||||
return remoteName;
|
||||
}
|
||||
|
||||
public IProject getLocal() {
|
||||
return local;
|
||||
}
|
||||
|
||||
}
|
@ -6,18 +6,20 @@ import org.eclipse.jface.text.IDocumentListener;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
import me.mrletsplay.shareclient.ShareClient;
|
||||
import me.mrletsplay.shareclient.util.ProjectAndPath;
|
||||
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
import me.mrletsplay.shareclient.util.SharedProject;
|
||||
import me.mrletsplay.shareclientcore.document.SharedDocument;
|
||||
|
||||
public class ShareClientDocumentListener implements IDocumentListener {
|
||||
|
||||
private ProjectRelativePath path;
|
||||
private ProjectAndPath path;
|
||||
private IDocument document;
|
||||
|
||||
private boolean ignoreChanges = false;
|
||||
|
||||
public ShareClientDocumentListener(ProjectRelativePath path, IDocument document) {
|
||||
public ShareClientDocumentListener(ProjectAndPath path, IDocument document) {
|
||||
this.path = path;
|
||||
this.document = document;
|
||||
}
|
||||
@ -40,7 +42,10 @@ public class ShareClientDocumentListener implements IDocumentListener {
|
||||
ShareSession session = ShareClient.getDefault().getActiveSession();
|
||||
if(session == null) return;
|
||||
|
||||
SharedDocument doc = session.getOrCreateSharedDocument(path, () -> event.fDocument.get());
|
||||
SharedProject sharedProject = session.getSharedProject(path.project());
|
||||
if(sharedProject == null) return;
|
||||
|
||||
SharedDocument doc = session.getOrCreateSharedDocument(new ProjectRelativePath(sharedProject.getRemoteName(), path.path()), () -> event.fDocument.get());
|
||||
if(event.getLength() > 0) {
|
||||
doc.localDelete(event.getOffset(), event.getLength());
|
||||
}
|
||||
|
@ -15,13 +15,17 @@ import org.eclipse.ui.IWorkbenchPartReference;
|
||||
import org.eclipse.ui.part.FileEditorInput;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
|
||||
import me.mrletsplay.shareclient.ShareClient;
|
||||
import me.mrletsplay.shareclient.util.ProjectAndPath;
|
||||
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
import me.mrletsplay.shareclient.util.SharedProject;
|
||||
|
||||
public class ShareClientPartListener implements IPartListener2 {
|
||||
|
||||
private Map<ProjectRelativePath, ShareClientDocumentListener> listeners = new HashMap<>();
|
||||
private Map<ProjectAndPath, ShareClientDocumentListener> listeners = new HashMap<>();
|
||||
|
||||
private ShareClientDocumentListener createListener(ProjectRelativePath path, IDocument document) {
|
||||
private ShareClientDocumentListener createListener(ProjectAndPath path, IDocument document) {
|
||||
if(listeners.containsKey(path)) return listeners.get(path);
|
||||
ShareClientDocumentListener listener = new ShareClientDocumentListener(path, document);
|
||||
listeners.put(path, listener);
|
||||
@ -43,7 +47,8 @@ public class ShareClientPartListener implements IPartListener2 {
|
||||
IProject project = file.getProject();
|
||||
|
||||
Path filePath = project.getLocation().toPath().relativize(file.getLocation().toPath());
|
||||
ProjectRelativePath relPath = new ProjectRelativePath(project.getName(), filePath.toString());
|
||||
// FIXME: this should probably just store an IProject with a relative path
|
||||
ProjectAndPath relPath = new ProjectAndPath(project, filePath.toString());
|
||||
System.out.println("Opened editor: " + relPath);
|
||||
document.addDocumentListener(createListener(relPath, document));
|
||||
}
|
||||
@ -58,8 +63,18 @@ public class ShareClientPartListener implements IPartListener2 {
|
||||
addDocumentListener(partRef);
|
||||
}
|
||||
|
||||
public Map<ProjectRelativePath, ShareClientDocumentListener> getListeners() {
|
||||
public Map<ProjectAndPath, ShareClientDocumentListener> getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public ShareClientDocumentListener getListener(ProjectRelativePath path) {
|
||||
ShareSession session = ShareClient.getDefault().getActiveSession();
|
||||
if(session == null) return null;
|
||||
|
||||
SharedProject project = session.getSharedProject(path.projectName());
|
||||
if(project == null) return null;
|
||||
|
||||
return listeners.get(new ProjectAndPath(project.getLocal(), path.relativePath()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ public class ShareView extends ViewPart {
|
||||
public void run() {
|
||||
Clipboard clipboard = new Clipboard(Display.getDefault());
|
||||
clipboard.setContents(new String[] {ShareClient.getDefault().getActiveSession().getSessionID()}, new Transfer[] {TextTransfer.getInstance()});
|
||||
showMessage("The session ID has been copied to the clipboard");
|
||||
}
|
||||
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user