From 9812d9bd2f105a56c61f513018c7dd8dc04a60ae Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Thu, 14 Dec 2023 20:50:36 +0100 Subject: [PATCH] Add editor listeners --- plugin.xml | 6 ++ .../mrletsplay/shareclient/ShareClient.java | 96 ++++++++++++++++--- .../handlers/ShareProjectHandler.java | 4 +- .../shareclient/util/ChecksumUtil.java | 22 +++++ .../listeners/ShareClientPageListener.java | 27 ++++++ .../listeners/ShareClientPartListener.java | 77 +++++++++++++++ .../listeners/ShareClientWindowListener.java | 28 ++++++ .../shareclient/views/ShareView.java | 3 - 8 files changed, 245 insertions(+), 18 deletions(-) create mode 100644 src/main/java/me/mrletsplay/shareclient/util/ChecksumUtil.java create mode 100644 src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPageListener.java create mode 100644 src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPartListener.java create mode 100644 src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientWindowListener.java diff --git a/plugin.xml b/plugin.xml index 511077b..138bafa 100644 --- a/plugin.xml +++ b/plugin.xml @@ -97,5 +97,11 @@ name="Share Client"> + + + + diff --git a/src/main/java/me/mrletsplay/shareclient/ShareClient.java b/src/main/java/me/mrletsplay/shareclient/ShareClient.java index 0efa03d..05f3a1c 100644 --- a/src/main/java/me/mrletsplay/shareclient/ShareClient.java +++ b/src/main/java/me/mrletsplay/shareclient/ShareClient.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -20,26 +21,38 @@ import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; +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.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.shareclientcore.connection.Change; import me.mrletsplay.shareclientcore.connection.ConnectionException; import me.mrletsplay.shareclientcore.connection.MessageListener; import me.mrletsplay.shareclientcore.connection.RemoteConnection; 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.Message; 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.document.SharedDocument; /** * 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 public static final String PLUGIN_ID = "ShareClient"; //$NON-NLS-1$ @@ -47,11 +60,15 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener { // The shared instance private static ShareClient plugin; + private ShareClientPartListener partListener = new ShareClientPartListener(); + private ShareView view; private ShareSession activeSession; - public ShareClient() { + private Map sharedDocuments; + public ShareClient() { + this.sharedDocuments = new HashMap<>(); } @Override @@ -147,6 +164,11 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener { updateView(); } + if(message instanceof PeerLeaveMessage leave) { + activeSession.getPeers().removeIf(p -> p.siteID() == leave.peerSiteID()); + updateView(); + } + if (message instanceof FullSyncMessage sync) { // TODO: handle FULL_SYNC ProjectRelativePath path; @@ -182,6 +204,8 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener { Files.createFile(filePath); } + // TODO: update sharedDocuments + Files.write(filePath, sync.content()); project.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (IOException | CoreException e) { @@ -227,18 +251,50 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener { RemoteConnection connection = activeSession.getConnection(); for (var en : paths.entrySet()) { - if (!Files.isRegularFile(en.getValue())) - continue; - try { - byte[] bytes = Files.readAllBytes(en.getValue()); - connection.send(new FullSyncMessage(req.siteID(), en.getKey().toString(), bytes)); - } catch (IOException | ConnectionException e) { - e.printStackTrace(); - MessageDialog.openError(null, "Share Client", "Failed to send file contents: " + e.toString()); - return; - } + if(!sendFullSyncOrChecksum(connection, req.siteID(), en.getKey(), en.getValue(), false)) return; } } + + if(message instanceof ChangeMessage change) { + Change c = change.change(); + try { + ProjectRelativePath path = ProjectRelativePath.of(c.documentPath()); + }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 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) { + e.printStackTrace(); + MessageDialog.openError(null, "Share Client", "Failed to send file contents: " + e.toString()); + return false; + } + + return true; } private Map 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)); + }); + }); + } + } diff --git a/src/main/java/me/mrletsplay/shareclient/handlers/ShareProjectHandler.java b/src/main/java/me/mrletsplay/shareclient/handlers/ShareProjectHandler.java index dcfc9a9..8b98a6d 100644 --- a/src/main/java/me/mrletsplay/shareclient/handlers/ShareProjectHandler.java +++ b/src/main/java/me/mrletsplay/shareclient/handlers/ShareProjectHandler.java @@ -35,9 +35,7 @@ public class ShareProjectHandler extends AbstractHandler { ShareSession session = ShareClient.getDefault().getOrStartSession(); if(session == null) return null; - session.getSharedProjects().add(project); - - // TODO: broadcast FULL_SYNC for newly added project + ShareClient.getDefault().addSharedProject(project); // IEditorPart editor = window.getActivePage().getActiveEditor(); // if(!(editor instanceof ITextEditor)) return null; diff --git a/src/main/java/me/mrletsplay/shareclient/util/ChecksumUtil.java b/src/main/java/me/mrletsplay/shareclient/util/ChecksumUtil.java new file mode 100644 index 0000000..49da555 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/util/ChecksumUtil.java @@ -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); + } + +} diff --git a/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPageListener.java b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPageListener.java new file mode 100644 index 0000000..6619071 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPageListener.java @@ -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) {} + +} diff --git a/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPartListener.java b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPartListener.java new file mode 100644 index 0000000..f97aaa9 --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientPartListener.java @@ -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 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); + } + +} diff --git a/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientWindowListener.java b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientWindowListener.java new file mode 100644 index 0000000..228403c --- /dev/null +++ b/src/main/java/me/mrletsplay/shareclient/util/listeners/ShareClientWindowListener.java @@ -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) {} + +} diff --git a/src/main/java/me/mrletsplay/shareclient/views/ShareView.java b/src/main/java/me/mrletsplay/shareclient/views/ShareView.java index 1ad49d5..e1df2e8 100644 --- a/src/main/java/me/mrletsplay/shareclient/views/ShareView.java +++ b/src/main/java/me/mrletsplay/shareclient/views/ShareView.java @@ -5,7 +5,6 @@ import java.util.Arrays; import javax.inject.Inject; -import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.dialogs.InputDialog; @@ -105,8 +104,6 @@ public class ShareView extends ViewPart { @Override 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); input.setBlockOnOpen(true); if(input.open() != InputDialog.OK) return;