Add debug dialog, Use bytes everywhere, Sync somewhat works (WIP)

This commit is contained in:
MrLetsplay 2024-06-05 22:06:46 +02:00
parent d47edc9e1a
commit 09d7d80d29
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
9 changed files with 158 additions and 78 deletions

BIN
icons/bug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

BIN
icons/bug@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

View File

@ -2,7 +2,6 @@ package me.mrletsplay.shareclient;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets;
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.Arrays;
@ -29,6 +28,7 @@ import org.osgi.framework.BundleContext;
import me.mrletsplay.shareclient.util.ChecksumUtil; import me.mrletsplay.shareclient.util.ChecksumUtil;
import me.mrletsplay.shareclient.util.Peer; import me.mrletsplay.shareclient.util.Peer;
import me.mrletsplay.shareclient.util.ProjectAndPath;
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.SharedProject; import me.mrletsplay.shareclient.util.SharedProject;
@ -51,6 +51,7 @@ 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.PeerLeaveMessage;
import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage; import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage;
import me.mrletsplay.shareclientcore.document.Char;
import me.mrletsplay.shareclientcore.document.SharedDocument; import me.mrletsplay.shareclientcore.document.SharedDocument;
/** /**
@ -230,17 +231,15 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
byte[] bytes = new byte[sync.content().size()]; byte[] bytes = new byte[sync.content().size()];
for(int i = 0; i < bytes.length; i++) { for(int i = 0; i < bytes.length; i++) {
bytes[i] = sync.content().get(i).value();
} }
String content = sync.content().stream()
.map(c -> String.valueOf(c.value()))
.collect(Collectors.joining());
SharedDocument sharedDocument = activeSession.getOrCreateSharedDocument(path, () -> content); SharedDocument sharedDocument = activeSession.getOrCreateSharedDocument(path, null /* because the content will be replaced afterwards anyway */);
// TODO: update sharedDocuments sharedDocument.clear();
for(Char c : sync.content()) sharedDocument.getCharBag().add(c);
if(listener != null) listener.setIgnoreChanges(true); if(listener != null) listener.setIgnoreChanges(true);
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8)); // TODO: may cause problems with binary files? but they shouldn't be shared in the first place, so... Files.write(filePath, bytes);
sharedProject.getLocal().refreshLocal(IResource.DEPTH_INFINITE, null); sharedProject.getLocal().refreshLocal(IResource.DEPTH_INFINITE, null);
if(listener != null) listener.setIgnoreChanges(false); if(listener != null) listener.setIgnoreChanges(false);
} catch (IOException | CoreException e) { } catch (IOException | CoreException e) {
@ -299,17 +298,13 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
return; return;
} }
if(activeSession.getSharedDocument(path) != null) return; SharedDocument doc = activeSession.getSharedDocument(path); // Doesn't use getOrCreateSharedDocument, because it doesn't make sense to apply a change to a document that wasn't previously shared
// FIXME: doesn't work if(doc == null) {
SharedDocument doc = activeSession.getOrCreateSharedDocument(path, () -> { // TODO: show proper synchronization issue warning, because all received changes should be in previously shared documents
try { MessageDialog.openError(null, "Share Client", "Synchronization issue (received change in non-shared document)");
return Files.readString(resolvePath(path)); return;
}catch(IOException e) { }
MessageDialog.openError(null, "Share Client", "Failed to read file: " + e.toString());
throw new RuntimeException(e);
}
});
doc.onMessage(message); doc.onMessage(message);
} }
@ -333,7 +328,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(shared).entrySet()) { for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(shared).entrySet()) {
activeSession.getOrCreateSharedDocument(en.getKey(), () -> { activeSession.getOrCreateSharedDocument(en.getKey(), () -> {
try { try {
return Files.readString(en.getValue(), StandardCharsets.UTF_8); return Files.readAllBytes(en.getValue());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
throw new RuntimeException(e); throw new RuntimeException(e);
@ -364,7 +359,13 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
return true; return true;
} }
private Path resolvePath(ProjectRelativePath path) { private ProjectAndPath resolveToProjectAndPath(ProjectRelativePath path) {
SharedProject sharedProject = activeSession.getSharedProject(path.projectName());
if(sharedProject == null) return null;
return new ProjectAndPath(sharedProject.getLocal(), path.relativePath());
}
private Path resolveToPath(ProjectRelativePath path) {
SharedProject sharedProject = activeSession.getSharedProject(path.projectName()); SharedProject sharedProject = activeSession.getSharedProject(path.projectName());
if(sharedProject == null) return null; if(sharedProject == null) return null;
Path projectLocation = sharedProject.getLocal().getLocation().toPath(); Path projectLocation = sharedProject.getLocal().getLocation().toPath();

View File

@ -21,6 +21,7 @@ public class ShareClientPreferencePage extends FieldEditorPreferencePage impleme
addField(new BooleanFieldEditor(ShareClientPreferences.SHOW_CURSORS, "Show other users' cursors (TODO)", getFieldEditorParent())); addField(new BooleanFieldEditor(ShareClientPreferences.SHOW_CURSORS, "Show other users' cursors (TODO)", getFieldEditorParent()));
addField(new BooleanFieldEditor(ShareClientPreferences.ONLY_SHARE_SOURCE_FOLDERS, "Only share files in source folders (TODO)", getFieldEditorParent())); addField(new BooleanFieldEditor(ShareClientPreferences.ONLY_SHARE_SOURCE_FOLDERS, "Only share files in source folders (TODO)", getFieldEditorParent()));
addField(new BooleanFieldEditor(ShareClientPreferences.USE_SHARECLIENT_PROJECT_CONFIG, "Use configuration from .shareclient file in the project (TODO)", getFieldEditorParent())); addField(new BooleanFieldEditor(ShareClientPreferences.USE_SHARECLIENT_PROJECT_CONFIG, "Use configuration from .shareclient file in the project (TODO)", getFieldEditorParent()));
addField(new BooleanFieldEditor(ShareClientPreferences.DEBUG_MODE, "Debug mode", getFieldEditorParent())); // TODO: update view on setting change
} }
@Override @Override

View File

@ -7,6 +7,7 @@ public class ShareClientPreferences {
USERNAME = "username", USERNAME = "username",
SHOW_CURSORS = "showCursors", SHOW_CURSORS = "showCursors",
ONLY_SHARE_SOURCE_FOLDERS = "onlyShareSourceFolders", // TODO: option to only share source folders of projects? ONLY_SHARE_SOURCE_FOLDERS = "onlyShareSourceFolders", // TODO: option to only share source folders of projects?
USE_SHARECLIENT_PROJECT_CONFIG = "useShareClientConfig"; // TODO: option to enable a .shareclient project-specific config file USE_SHARECLIENT_PROJECT_CONFIG = "useShareClientConfig", // TODO: option to enable a .shareclient project-specific config file
DEBUG_MODE = "debugMode";
} }

View File

@ -8,9 +8,13 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import me.mrletsplay.shareclient.ShareClient; import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.util.listeners.ShareClientDocumentListener;
import me.mrletsplay.shareclientcore.connection.RemoteConnection; import me.mrletsplay.shareclientcore.connection.RemoteConnection;
import me.mrletsplay.shareclientcore.document.DocumentListener;
import me.mrletsplay.shareclientcore.document.SharedDocument; import me.mrletsplay.shareclientcore.document.SharedDocument;
public class ShareSession { public class ShareSession {
@ -79,51 +83,48 @@ public class ShareSession {
public SharedDocument getOrCreateSharedDocument(ProjectRelativePath path, Supplier<byte[]> initialContents) { public SharedDocument getOrCreateSharedDocument(ProjectRelativePath path, Supplier<byte[]> initialContents) {
return sharedDocuments.computeIfAbsent(path, p -> { return sharedDocuments.computeIfAbsent(path, p -> {
SharedDocument doc = new SharedDocument(connection, path.toString(), initialContents.get()); SharedDocument doc = new SharedDocument(connection, path.toString(), initialContents == null ? null : initialContents.get());
// doc.addListener(new DocumentListener() { doc.addListener(new DocumentListener() {
//
// @Override @Override
// public void onInsert(int index, byte character) { public void onInsert(int index, byte character) {
// // FIXME: potential desync. If changes arrive while waiting for asyncExec to execute, it will insert at the wrong position // TODO: assert that we are on the UI thread
// Display.getDefault().asyncExec(() -> { ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
// ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path); if(documentListener != null) {
// if(documentListener != null) { IDocument document = documentListener.getDocument();
// IDocument document = documentListener.getDocument();
// documentListener.setIgnoreChanges(true);
// documentListener.setIgnoreChanges(true); try {
// try { document.replace(index, 0, String.valueOf((char) character)); // FIXME: dangerous! will explode on multi-byte characters
// document.replace(index, 0, String.valueOf(character)); } catch (BadLocationException e) {
// } catch (BadLocationException e) { e.printStackTrace();
// e.printStackTrace(); // TODO: treat as inconsistency
// // TODO: treat as inconsistency // MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
//// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString()); }
// } documentListener.setIgnoreChanges(false);
// documentListener.setIgnoreChanges(false); }
// } }
// });
// } @Override
// public void onDelete(int index) {
// @Override // TODO: assert that we are on the UI thread
// public void onDelete(int index) { ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
// Display.getDefault().asyncExec(() -> { if(documentListener != null) {
// ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path); IDocument document = documentListener.getDocument();
// if(documentListener != null) {
// IDocument document = documentListener.getDocument(); documentListener.setIgnoreChanges(true);
// try {
// documentListener.setIgnoreChanges(true); document.replace(index, 1, "");
// try { } catch (BadLocationException e) {
// document.replace(index, 1, ""); e.printStackTrace();
// } catch (BadLocationException e) { // TODO: treat as inconsistency
// e.printStackTrace(); // MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
// // TODO: treat as inconsistency }
//// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString()); documentListener.setIgnoreChanges(false);
// } }
// documentListener.setIgnoreChanges(false); }
// } });
// });
// }
// });
return doc; return doc;
}); });

View File

@ -1,9 +1,10 @@
package me.mrletsplay.shareclient.util.listeners; package me.mrletsplay.shareclient.util.listeners;
import java.nio.charset.StandardCharsets;
import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.swt.widgets.Display;
import me.mrletsplay.shareclient.ShareClient; import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.util.ProjectAndPath; import me.mrletsplay.shareclient.util.ProjectAndPath;
@ -38,20 +39,20 @@ public class ShareClientDocumentListener implements IDocumentListener {
System.out.println("UPDATE ON THREAD " + Thread.currentThread()); System.out.println("UPDATE ON THREAD " + Thread.currentThread());
Display.getDefault().asyncExec(() -> { // FIXME: bug somewhere here, can cause discrepancies in local editor when editing while changes are coming in
ShareSession session = ShareClient.getDefault().getActiveSession();
if(session == null) return;
SharedProject sharedProject = session.getSharedProject(path.project()); ShareSession session = ShareClient.getDefault().getActiveSession();
if(sharedProject == null) return; if(session == null) return;
SharedDocument doc = session.getOrCreateSharedDocument(new ProjectRelativePath(sharedProject.getRemoteName(), path.path()), () -> event.fDocument.get()); SharedProject sharedProject = session.getSharedProject(path.project());
if(event.getLength() > 0) { if(sharedProject == null) return;
doc.localDelete(event.getOffset(), event.getLength());
}
doc.localInsert(event.getOffset(), event.getText()); SharedDocument doc = session.getOrCreateSharedDocument(new ProjectRelativePath(sharedProject.getRemoteName(), path.path()), () -> event.fDocument.get().getBytes(StandardCharsets.UTF_8));
}); if(event.getLength() > 0) {
doc.localDelete(event.getOffset(), event.getLength());
}
doc.localInsert(event.getOffset(), event.getText());
} }
@Override @Override

View File

@ -0,0 +1,43 @@
package me.mrletsplay.shareclient.views;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class ScrollableDialog extends TitleAreaDialog {
private String text;
public ScrollableDialog(Shell parentShell, String title, String text) {
super(parentShell);
setTitle(title);
this.text = text;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
GridData gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessVerticalSpace = true;
gridData.verticalAlignment = GridData.FILL;
Text scrollable = new Text(composite, SWT.BORDER | SWT.V_SCROLL);
scrollable.setLayoutData(gridData);
scrollable.setText(text);
return composite;
}
@Override
protected boolean isResizable() {
return true;
}
}

View File

@ -31,6 +31,7 @@ import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.part.ViewPart;
import me.mrletsplay.shareclient.ShareClient; import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.ShareClientPreferences;
import me.mrletsplay.shareclient.util.ShareSession; import me.mrletsplay.shareclient.util.ShareSession;
import me.mrletsplay.shareclientcore.connection.ConnectionException; import me.mrletsplay.shareclientcore.connection.ConnectionException;
import me.mrletsplay.shareclientcore.connection.RemoteConnection; import me.mrletsplay.shareclientcore.connection.RemoteConnection;
@ -161,6 +162,33 @@ public class ShareView extends ViewPart {
}; };
Action debug = new Action("Debug", ImageDescriptor.createFromFile(ShareView.class, "/icons/bug.png")) {
@Override
public void run() {
ShareSession session = ShareClient.getDefault().getActiveSession();
if(session == null) {
MessageDialog.openInformation(null, "Debug", "No session running");
return;
}
StringBuilder sb = new StringBuilder();
session.getSharedDocuments().forEach((path, document) -> {
sb.append("--- Document: ").append(path).append('\n');
sb.append(document.getContentsAsString());
sb.append('\n');
sb.append('\n');
});
// MessageDialog.openInformation(null, "Debug Info", sb.toString());
new ScrollableDialog(null, "Debug Info", sb.toString()).open();
}
};
if(ShareClient.getDefault().getActiveSession() == null) { if(ShareClient.getDefault().getActiveSession() == null) {
toolbars.add(joinSession); toolbars.add(joinSession);
}else { }else {
@ -170,6 +198,10 @@ public class ShareView extends ViewPart {
toolbars.add(showSettings); toolbars.add(showSettings);
if(ShareClient.getDefault().getPreferenceStore().getBoolean(ShareClientPreferences.DEBUG_MODE)) {
toolbars.add(debug);
}
bars.updateActionBars(); bars.updateActionBars();
} }