Add editor listeners

This commit is contained in:
MrLetsplay 2023-12-14 20:50:36 +01:00
parent 050b09d1aa
commit 9812d9bd2f
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
8 changed files with 245 additions and 18 deletions

View File

@ -97,5 +97,11 @@
name="Share Client"> name="Share Client">
</page> </page>
</extension> </extension>
<extension
point="org.eclipse.ui.startup">
<startup
class="me.mrletsplay.shareclient.ShareClient">
</startup>
</extension>
</plugin> </plugin>

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@ -20,26 +21,38 @@ import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IStartup;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import me.mrletsplay.shareclient.util.ChecksumUtil;
import me.mrletsplay.shareclient.util.Peer; import me.mrletsplay.shareclient.util.Peer;
import me.mrletsplay.shareclient.util.ProjectRelativePath; import me.mrletsplay.shareclient.util.ProjectRelativePath;
import me.mrletsplay.shareclient.util.ShareSession; import me.mrletsplay.shareclient.util.ShareSession;
import me.mrletsplay.shareclient.util.listeners.ShareClientPageListener;
import me.mrletsplay.shareclient.util.listeners.ShareClientPartListener;
import me.mrletsplay.shareclient.util.listeners.ShareClientWindowListener;
import me.mrletsplay.shareclient.views.ShareView; import me.mrletsplay.shareclient.views.ShareView;
import me.mrletsplay.shareclientcore.connection.Change;
import me.mrletsplay.shareclientcore.connection.ConnectionException; import me.mrletsplay.shareclientcore.connection.ConnectionException;
import me.mrletsplay.shareclientcore.connection.MessageListener; import me.mrletsplay.shareclientcore.connection.MessageListener;
import me.mrletsplay.shareclientcore.connection.RemoteConnection; import me.mrletsplay.shareclientcore.connection.RemoteConnection;
import me.mrletsplay.shareclientcore.connection.WebSocketConnection; import me.mrletsplay.shareclientcore.connection.WebSocketConnection;
import me.mrletsplay.shareclientcore.connection.message.AddressableMessage;
import me.mrletsplay.shareclientcore.connection.message.ChangeMessage;
import me.mrletsplay.shareclientcore.connection.message.ChecksumMessage;
import me.mrletsplay.shareclientcore.connection.message.FullSyncMessage; import me.mrletsplay.shareclientcore.connection.message.FullSyncMessage;
import me.mrletsplay.shareclientcore.connection.message.Message; import me.mrletsplay.shareclientcore.connection.message.Message;
import me.mrletsplay.shareclientcore.connection.message.PeerJoinMessage; import me.mrletsplay.shareclientcore.connection.message.PeerJoinMessage;
import me.mrletsplay.shareclientcore.connection.message.PeerLeaveMessage;
import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage; import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage;
import me.mrletsplay.shareclientcore.document.SharedDocument;
/** /**
* The activator class controls the plug-in life cycle * The activator class controls the plug-in life cycle
*/ */
public class ShareClient extends AbstractUIPlugin implements MessageListener { public class ShareClient extends AbstractUIPlugin implements MessageListener, IStartup {
// The plug-in ID // The plug-in ID
public static final String PLUGIN_ID = "ShareClient"; //$NON-NLS-1$ public static final String PLUGIN_ID = "ShareClient"; //$NON-NLS-1$
@ -47,11 +60,15 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
// The shared instance // The shared instance
private static ShareClient plugin; private static ShareClient plugin;
private ShareClientPartListener partListener = new ShareClientPartListener();
private ShareView view; private ShareView view;
private ShareSession activeSession; private ShareSession activeSession;
public ShareClient() { private Map<ProjectRelativePath, SharedDocument> sharedDocuments;
public ShareClient() {
this.sharedDocuments = new HashMap<>();
} }
@Override @Override
@ -147,6 +164,11 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
updateView(); updateView();
} }
if(message instanceof PeerLeaveMessage leave) {
activeSession.getPeers().removeIf(p -> p.siteID() == leave.peerSiteID());
updateView();
}
if (message instanceof FullSyncMessage sync) { if (message instanceof FullSyncMessage sync) {
// TODO: handle FULL_SYNC // TODO: handle FULL_SYNC
ProjectRelativePath path; ProjectRelativePath path;
@ -182,6 +204,8 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
Files.createFile(filePath); Files.createFile(filePath);
} }
// TODO: update sharedDocuments
Files.write(filePath, sync.content()); Files.write(filePath, sync.content());
project.refreshLocal(IResource.DEPTH_INFINITE, null); project.refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (IOException | CoreException e) { } catch (IOException | CoreException e) {
@ -227,18 +251,50 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
RemoteConnection connection = activeSession.getConnection(); RemoteConnection connection = activeSession.getConnection();
for (var en : paths.entrySet()) { for (var en : paths.entrySet()) {
if (!Files.isRegularFile(en.getValue())) if(!sendFullSyncOrChecksum(connection, req.siteID(), en.getKey(), en.getValue(), false)) return;
continue; }
}
if(message instanceof ChangeMessage change) {
Change c = change.change();
try { try {
byte[] bytes = Files.readAllBytes(en.getValue()); ProjectRelativePath path = ProjectRelativePath.of(c.documentPath());
connection.send(new FullSyncMessage(req.siteID(), en.getKey().toString(), bytes)); }catch(IllegalArgumentException e) {
return;
}
// TODO: insert change into document in sharedDocuments
}
}
public void addSharedProject(IProject project) {
activeSession.getSharedProjects().add(project);
RemoteConnection connection = activeSession.getConnection();
for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(project).entrySet()) {
// TODO: add new document to sharedDocuments
if(!sendFullSyncOrChecksum(connection, AddressableMessage.BROADCAST_SITE_ID, en.getKey(), en.getValue(), false)) return;
}
}
private boolean sendFullSyncOrChecksum(RemoteConnection connection, int siteID, ProjectRelativePath relativePath, Path filePath, boolean checksum) {
if (!Files.isRegularFile(filePath)) return false;
try {
byte[] bytes = Files.readAllBytes(filePath);
if(!checksum) {
connection.send(new FullSyncMessage(siteID, relativePath.toString(), bytes));
}else {
connection.send(new ChecksumMessage(siteID, relativePath.toString(), ChecksumUtil.generateSHA256(bytes)));
}
} catch (IOException | ConnectionException e) { } catch (IOException | ConnectionException e) {
e.printStackTrace(); e.printStackTrace();
MessageDialog.openError(null, "Share Client", "Failed to send file contents: " + e.toString()); MessageDialog.openError(null, "Share Client", "Failed to send file contents: " + e.toString());
return; return false;
}
}
} }
return true;
} }
private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) { private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) {
@ -255,4 +311,20 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
} }
} }
public ShareClientPartListener getPartListener() {
return partListener;
}
@Override
public void earlyStartup() {
PlatformUI.getWorkbench().addWindowListener(ShareClientWindowListener.INSTANCE);
Arrays.stream(PlatformUI.getWorkbench().getWorkbenchWindows()).forEach(w -> {
w.addPageListener(ShareClientPageListener.INSTANCE);
Arrays.stream(w.getPages()).forEach(p -> {
p.addPartListener(partListener);
Arrays.stream(p.getEditorReferences()).forEach(e -> partListener.addDocumentListener(e));
});
});
}
} }

View File

@ -35,9 +35,7 @@ public class ShareProjectHandler extends AbstractHandler {
ShareSession session = ShareClient.getDefault().getOrStartSession(); ShareSession session = ShareClient.getDefault().getOrStartSession();
if(session == null) return null; if(session == null) return null;
session.getSharedProjects().add(project); ShareClient.getDefault().addSharedProject(project);
// TODO: broadcast FULL_SYNC for newly added project
// IEditorPart editor = window.getActivePage().getActiveEditor(); // IEditorPart editor = window.getActivePage().getActiveEditor();
// if(!(editor instanceof ITextEditor)) return null; // if(!(editor instanceof ITextEditor)) return null;

View File

@ -0,0 +1,22 @@
package me.mrletsplay.shareclient.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class ChecksumUtil {
private static final MessageDigest SHA_256;
static {
try {
SHA_256 = MessageDigest.getInstance("SHA-256");
}catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] generateSHA256(byte[] bytes) {
return SHA_256.digest(bytes);
}
}

View File

@ -0,0 +1,27 @@
package me.mrletsplay.shareclient.util.listeners;
import org.eclipse.ui.IPageListener;
import org.eclipse.ui.IWorkbenchPage;
import me.mrletsplay.shareclient.ShareClient;
public class ShareClientPageListener implements IPageListener {
public static final ShareClientPageListener INSTANCE = new ShareClientPageListener();
private ShareClientPageListener() {}
@Override
public void pageOpened(IWorkbenchPage page) {
page.addPartListener(ShareClient.getDefault().getPartListener());
}
@Override
public void pageClosed(IWorkbenchPage page) {
page.removePartListener(ShareClient.getDefault().getPartListener());
}
@Override
public void pageActivated(IWorkbenchPage page) {}
}

View File

@ -0,0 +1,77 @@
package me.mrletsplay.shareclient.util.listeners;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
import me.mrletsplay.shareclient.util.ProjectRelativePath;
public class ShareClientPartListener implements IPartListener2 {
private Map<ProjectRelativePath, IDocumentListener> listeners = new HashMap<>();
private IDocumentListener createListener(ProjectRelativePath path) {
if(listeners.containsKey(path)) return listeners.get(path);
IDocumentListener listener = new IDocumentListener() {
private boolean ignoreChanges = false;
@Override
public void documentChanged(DocumentEvent event) {
System.out.println("Change in document at " + path);
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
};
listeners.put(path, listener);
return listener;
}
public void addDocumentListener(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if(!(part instanceof IEditorPart)) return;
IEditorPart editor = (IEditorPart) part;
if(!(editor instanceof ITextEditor)) return;
ITextEditor textEditor = (ITextEditor) editor;
IEditorInput editorInput = editor.getEditorInput();
if(!(editorInput instanceof FileEditorInput)) return;
FileEditorInput fileEditorInput = (FileEditorInput) editorInput;
IDocument document = textEditor.getDocumentProvider().getDocument(editorInput);
IFile file = fileEditorInput.getFile();
IProject project = file.getProject();
Path filePath = project.getLocation().toPath().relativize(file.getLocation().toPath());
ProjectRelativePath relPath = new ProjectRelativePath(project.getName(), filePath.toString());
System.out.println(relPath);
document.addDocumentListener(createListener(relPath));
}
@Override
public void partOpened(IWorkbenchPartReference partRef) {
addDocumentListener(partRef);
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
addDocumentListener(partRef);
}
}

View File

@ -0,0 +1,28 @@
package me.mrletsplay.shareclient.util.listeners;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchWindow;
public class ShareClientWindowListener implements IWindowListener {
public static final ShareClientWindowListener INSTANCE = new ShareClientWindowListener();
private ShareClientWindowListener() {}
@Override
public void windowOpened(IWorkbenchWindow window) {
window.addPageListener(ShareClientPageListener.INSTANCE);
}
@Override
public void windowClosed(IWorkbenchWindow window) {
window.removePageListener(ShareClientPageListener.INSTANCE);
}
@Override
public void windowActivated(IWorkbenchWindow window) {}
@Override
public void windowDeactivated(IWorkbenchWindow window) {}
}

View File

@ -5,7 +5,6 @@ import java.util.Arrays;
import javax.inject.Inject; import javax.inject.Inject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.action.Action; import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.InputDialog;
@ -105,8 +104,6 @@ public class ShareView extends ViewPart {
@Override @Override
public void run() { public void run() {
showMessage(Arrays.stream(ResourcesPlugin.getWorkspace().getRoot().getProjects()).map(p -> p.getName() + ": " + p.getLocation().toString()).toList().toString());
InputDialog input = new InputDialog(viewer.getControl().getShell(), "Join session", "Enter session id", "EEE", null); InputDialog input = new InputDialog(viewer.getControl().getShell(), "Join session", "Enter session id", "EEE", null);
input.setBlockOnOpen(true); input.setBlockOnOpen(true);
if(input.open() != InputDialog.OK) return; if(input.open() != InputDialog.OK) return;