Initial sync
This commit is contained in:
parent
2541fdbd58
commit
050b09d1aa
@ -10,8 +10,9 @@ Require-Bundle: org.eclipse.ui,
|
||||
org.eclipse.ui.editors;bundle-version="3.17.0",
|
||||
org.eclipse.text,
|
||||
Java-WebSocket;bundle-version="1.5.4",
|
||||
wrapped.me.mrletsplay.ShareClient-Core;bundle-version="1.0.0",
|
||||
org.eclipse.core.resources;bundle-version="3.19.100"
|
||||
org.eclipse.core.resources;bundle-version="3.19.100",
|
||||
wrapped.me.mrletsplay.ShareLib;bundle-version="1.0.0",
|
||||
org.eclipse.ui.ide;bundle-version="3.22.0"
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-17
|
||||
Automatic-Module-Name: ShareClient
|
||||
Bundle-ActivationPolicy: lazy
|
||||
|
BIN
icons/content-copy.png
Normal file
BIN
icons/content-copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 317 B |
BIN
icons/content-copy@2x.png
Normal file
BIN
icons/content-copy@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 452 B |
@ -4,13 +4,20 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IProjectDescription;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IWorkspace;
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
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.plugin.AbstractUIPlugin;
|
||||
@ -18,6 +25,7 @@ import org.osgi.framework.BundleContext;
|
||||
|
||||
import me.mrletsplay.shareclient.util.Peer;
|
||||
import me.mrletsplay.shareclient.util.ProjectRelativePath;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
import me.mrletsplay.shareclient.views.ShareView;
|
||||
import me.mrletsplay.shareclientcore.connection.ConnectionException;
|
||||
import me.mrletsplay.shareclientcore.connection.MessageListener;
|
||||
@ -26,6 +34,7 @@ import me.mrletsplay.shareclientcore.connection.WebSocketConnection;
|
||||
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.RequestFullSyncMessage;
|
||||
|
||||
/**
|
||||
* The activator class controls the plug-in life cycle
|
||||
@ -38,15 +47,11 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
// The shared instance
|
||||
private static ShareClient plugin;
|
||||
|
||||
private RemoteConnection activeConnection;
|
||||
private ShareView view;
|
||||
private List<Peer> peers;
|
||||
private ShareSession activeSession;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*/
|
||||
public ShareClient() {
|
||||
this.peers = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -55,6 +60,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
plugin = this;
|
||||
|
||||
getPreferenceStore().setDefault(ShareClientPreferences.SERVER_URI, "ws://localhost:5473");
|
||||
getPreferenceStore().setDefault(ShareClientPreferences.SHOW_CURSORS, true);
|
||||
// new ShareWSClient(URI.create("ws://localhost:5473")).connect();
|
||||
}
|
||||
|
||||
@ -73,52 +79,56 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
public void updateView() {
|
||||
if (view == null) return;
|
||||
Display.getDefault().asyncExec(() -> view.getViewer().setInput(peers.stream().map(p -> p.name()).toArray(String[]::new)));
|
||||
Display.getDefault().asyncExec(() -> view.updateActionBars());
|
||||
|
||||
if (activeSession == null) return;
|
||||
String[] peerNames = activeSession.getPeers().stream().map(p -> p.name()).toArray(String[]::new);
|
||||
Display.getDefault().asyncExec(() -> view.getViewer().setInput(peerNames));
|
||||
}
|
||||
|
||||
public RemoteConnection openConnection(String sessionID) {
|
||||
public ShareSession startSession(String sessionID) {
|
||||
String serverURI = getPreferenceStore().getString(ShareClientPreferences.SERVER_URI);
|
||||
if (serverURI == null) return null;
|
||||
|
||||
String username = getPreferenceStore().getString(ShareClientPreferences.USERNAME);
|
||||
if(username == null || username.isBlank()) username = "user" + new Random().nextInt(1000);
|
||||
if (username == null || username.isBlank())
|
||||
username = "user" + new Random().nextInt(1000);
|
||||
|
||||
WebSocketConnection connection = new WebSocketConnection(URI.create(serverURI), username);
|
||||
try {
|
||||
connection.connect(sessionID); // TODO: connect to existing session
|
||||
} catch (ConnectionException e) {
|
||||
MessageDialog.openInformation(
|
||||
null,
|
||||
"Share Client",
|
||||
"Failed to connect to server: " + e);
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to connect to server: " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
connection.addListener(this);
|
||||
|
||||
return activeConnection = connection;
|
||||
updateView();
|
||||
return activeSession = new ShareSession(connection, sessionID);
|
||||
}
|
||||
|
||||
public RemoteConnection getOrOpenConnection() {
|
||||
if(activeConnection == null) {
|
||||
openConnection(UUID.randomUUID().toString());
|
||||
public ShareSession getOrStartSession() {
|
||||
if (activeSession == null) {
|
||||
startSession(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
return activeConnection;
|
||||
return activeSession;
|
||||
}
|
||||
|
||||
public void closeConnection() {
|
||||
if(activeConnection == null) return;
|
||||
activeConnection.disconnect();
|
||||
activeConnection = null;
|
||||
peers.clear();
|
||||
if (activeSession == null) return;
|
||||
ShareSession session = activeSession;
|
||||
activeSession = null;
|
||||
session.stop();
|
||||
updateView();
|
||||
}
|
||||
|
||||
public RemoteConnection getActiveConnection() {
|
||||
return activeConnection;
|
||||
public ShareSession getActiveSession() {
|
||||
return activeSession;
|
||||
}
|
||||
|
||||
public void setView(ShareView view) {
|
||||
@ -133,7 +143,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
public void onMessage(Message message) {
|
||||
System.out.println("Got: " + message);
|
||||
if (message instanceof PeerJoinMessage join) {
|
||||
peers.add(new Peer(join.peerName(), join.peerSiteID()));
|
||||
activeSession.getPeers().add(new Peer(join.peerName(), join.peerSiteID()));
|
||||
updateView();
|
||||
}
|
||||
|
||||
@ -143,11 +153,27 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
try {
|
||||
path = ProjectRelativePath.of(sync.documentPath());
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.projectName());
|
||||
if(project == null) return; // TODO: create project
|
||||
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;
|
||||
}
|
||||
System.out.println("Created project " + project);
|
||||
}
|
||||
|
||||
Path filePath = project.getLocation().toPath().resolve(path.relativePath());
|
||||
try {
|
||||
@ -157,10 +183,76 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener {
|
||||
}
|
||||
|
||||
Files.write(filePath, sync.content());
|
||||
} catch (IOException e) {
|
||||
// TODO: handle exception
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, null);
|
||||
} catch (IOException | CoreException e) {
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to update file: " + e.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (message instanceof RequestFullSyncMessage req) {
|
||||
Map<ProjectRelativePath, Path> paths = new HashMap<>();
|
||||
if (req.documentPath() == null) {
|
||||
// Sync entire (shared) workspace
|
||||
for (IProject project : activeSession.getSharedProjects()) {
|
||||
var files = getProjectFiles(project);
|
||||
if (files == null) return;
|
||||
paths.putAll(files);
|
||||
}
|
||||
} else {
|
||||
ProjectRelativePath path;
|
||||
try {
|
||||
path = ProjectRelativePath.of(req.documentPath());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.projectName());
|
||||
if (project == null || !project.exists()) return;
|
||||
if (!activeSession.getSharedProjects().contains(project)) return;
|
||||
|
||||
if (!path.relativePath().isEmpty()) {
|
||||
Path projectLocation = project.getLocation().toPath();
|
||||
Path filePath = projectLocation.resolve(path.relativePath()).normalize();
|
||||
if (!filePath.startsWith(projectLocation)) return;
|
||||
paths.put(new ProjectRelativePath(project.getName(), path.relativePath()), filePath);
|
||||
} else {
|
||||
// Sync entire project
|
||||
var files = getProjectFiles(project);
|
||||
if (files == null) return;
|
||||
paths.putAll(files);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<ProjectRelativePath, Path> getProjectFiles(IProject project) {
|
||||
try {
|
||||
Path projectLocation = project.getLocation().toPath();
|
||||
return Files.walk(projectLocation)
|
||||
.collect(Collectors.toMap(
|
||||
p -> new ProjectRelativePath(project.getName(), projectLocation.relativize(p).toString()),
|
||||
Function.identity()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MessageDialog.openError(null, "Share Client", "Failed to collect project files: " + e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
|
||||
import me.mrletsplay.shareclient.ShareClient;
|
||||
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
|
||||
public class ShareProjectHandler extends AbstractHandler {
|
||||
|
||||
@ -32,10 +32,12 @@ public class ShareProjectHandler extends AbstractHandler {
|
||||
IPath path = project.getLocation();
|
||||
if(path == null) return null;
|
||||
|
||||
RemoteConnection con = ShareClient.getDefault().getOrOpenConnection();
|
||||
if(con == null) return null;
|
||||
ShareSession session = ShareClient.getDefault().getOrStartSession();
|
||||
if(session == null) return null;
|
||||
|
||||
// TODO: handle case when adding project to existing session
|
||||
session.getSharedProjects().add(project);
|
||||
|
||||
// TODO: broadcast FULL_SYNC for newly added project
|
||||
|
||||
// IEditorPart editor = window.getActivePage().getActiveEditor();
|
||||
// if(!(editor instanceof ITextEditor)) return null;
|
||||
|
@ -0,0 +1,55 @@
|
||||
package me.mrletsplay.shareclient.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
||||
import me.mrletsplay.shareclient.ShareClient;
|
||||
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
||||
|
||||
public class ShareSession {
|
||||
|
||||
private RemoteConnection connection;
|
||||
private String sessionID;
|
||||
private List<IProject> sharedProjects;
|
||||
private List<Peer> peers;
|
||||
|
||||
public ShareSession(RemoteConnection connection, String sessionID) {
|
||||
this.connection = connection;
|
||||
this.sessionID = sessionID;
|
||||
this.sharedProjects = new ArrayList<>();
|
||||
this.peers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public RemoteConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public String getSessionID() {
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
public List<IProject> getSharedProjects() {
|
||||
return sharedProjects;
|
||||
}
|
||||
|
||||
public List<Peer> getPeers() {
|
||||
return peers;
|
||||
}
|
||||
|
||||
public boolean hasRemotePeer() {
|
||||
return peers.stream().anyMatch(p -> p.siteID() != connection.getSiteID());
|
||||
}
|
||||
|
||||
public boolean isHost() {
|
||||
return peers.stream().max(Comparator.comparingInt(p -> p.siteID())).get().siteID() == connection.getSiteID();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
connection.disconnect();
|
||||
ShareClient.getDefault().updateView();
|
||||
}
|
||||
|
||||
}
|
@ -18,8 +18,12 @@ import org.eclipse.jface.viewers.LabelProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TableViewer;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.dnd.Clipboard;
|
||||
import org.eclipse.swt.dnd.TextTransfer;
|
||||
import org.eclipse.swt.dnd.Transfer;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.IActionBars;
|
||||
import org.eclipse.ui.IViewSite;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
@ -28,6 +32,7 @@ import org.eclipse.ui.dialogs.PreferencesUtil;
|
||||
import org.eclipse.ui.part.ViewPart;
|
||||
|
||||
import me.mrletsplay.shareclient.ShareClient;
|
||||
import me.mrletsplay.shareclient.util.ShareSession;
|
||||
import me.mrletsplay.shareclientcore.connection.ConnectionException;
|
||||
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
|
||||
import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage;
|
||||
@ -90,7 +95,7 @@ public class ShareView extends ViewPart {
|
||||
updateActionBars();
|
||||
}
|
||||
|
||||
private void updateActionBars() {
|
||||
public void updateActionBars() {
|
||||
IActionBars bars = getViewSite().getActionBars();
|
||||
IToolBarManager toolbars = bars.getToolBarManager();
|
||||
|
||||
@ -106,11 +111,13 @@ public class ShareView extends ViewPart {
|
||||
input.setBlockOnOpen(true);
|
||||
if(input.open() != InputDialog.OK) return;
|
||||
|
||||
RemoteConnection connection = ShareClient.getDefault().getActiveConnection();
|
||||
if(connection != null) connection.disconnect();
|
||||
ShareSession session = ShareClient.getDefault().getActiveSession();
|
||||
if(session != null) session.stop();
|
||||
|
||||
connection = ShareClient.getDefault().openConnection(input.getValue());
|
||||
if(connection == null) return;
|
||||
session = ShareClient.getDefault().startSession(input.getValue());
|
||||
if(session == null) return;
|
||||
|
||||
RemoteConnection connection = session.getConnection();
|
||||
|
||||
try {
|
||||
connection.send(new RequestFullSyncMessage(connection.getSiteID(), null));
|
||||
@ -122,7 +129,17 @@ public class ShareView extends ViewPart {
|
||||
}
|
||||
|
||||
};
|
||||
if(ShareClient.getDefault().getActiveConnection() == null) toolbars.add(joinSession);
|
||||
|
||||
Action copySessionID = new Action("Copy session ID", ImageDescriptor.createFromFile(ShareView.class, "/icons/content-copy.png")) {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Clipboard clipboard = new Clipboard(Display.getDefault());
|
||||
clipboard.setContents(new String[] {ShareClient.getDefault().getActiveSession().getSessionID()}, new Transfer[] {TextTransfer.getInstance()});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
Action leaveSession = new Action("Leave session", ImageDescriptor.createFromFile(ShareView.class, "/icons/stop.png")) {
|
||||
|
||||
@ -133,7 +150,6 @@ public class ShareView extends ViewPart {
|
||||
}
|
||||
|
||||
};
|
||||
if(ShareClient.getDefault().getActiveConnection() != null) toolbars.add(leaveSession);
|
||||
|
||||
Action showSettings = new Action("Settings", ImageDescriptor.createFromFile(ShareView.class, "/icons/cog.png")) {
|
||||
|
||||
@ -146,6 +162,14 @@ public class ShareView extends ViewPart {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if(ShareClient.getDefault().getActiveSession() == null) {
|
||||
toolbars.add(joinSession);
|
||||
}else {
|
||||
toolbars.add(copySessionID);
|
||||
toolbars.add(leaveSession);
|
||||
}
|
||||
|
||||
toolbars.add(showSettings);
|
||||
|
||||
bars.updateActionBars();
|
||||
|
Loading…
Reference in New Issue
Block a user