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.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@ -29,6 +28,7 @@ import org.osgi.framework.BundleContext;
import me.mrletsplay.shareclient.util.ChecksumUtil;
import me.mrletsplay.shareclient.util.Peer;
import me.mrletsplay.shareclient.util.ProjectAndPath;
import me.mrletsplay.shareclient.util.ProjectRelativePath;
import me.mrletsplay.shareclient.util.ShareSession;
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.PeerLeaveMessage;
import me.mrletsplay.shareclientcore.connection.message.RequestFullSyncMessage;
import me.mrletsplay.shareclientcore.document.Char;
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()];
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);
// TODO: update sharedDocuments
SharedDocument sharedDocument = activeSession.getOrCreateSharedDocument(path, null /* because the content will be replaced afterwards anyway */);
sharedDocument.clear();
for(Char c : sync.content()) sharedDocument.getCharBag().add(c);
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);
if(listener != null) listener.setIgnoreChanges(false);
} catch (IOException | CoreException e) {
@ -299,17 +298,13 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
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
SharedDocument doc = activeSession.getOrCreateSharedDocument(path, () -> {
try {
return Files.readString(resolvePath(path));
}catch(IOException e) {
MessageDialog.openError(null, "Share Client", "Failed to read file: " + e.toString());
throw new RuntimeException(e);
}
});
if(doc == null) {
// TODO: show proper synchronization issue warning, because all received changes should be in previously shared documents
MessageDialog.openError(null, "Share Client", "Synchronization issue (received change in non-shared document)");
return;
}
doc.onMessage(message);
}
@ -333,7 +328,7 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
for(Map.Entry<ProjectRelativePath, Path> en : getProjectFiles(shared).entrySet()) {
activeSession.getOrCreateSharedDocument(en.getKey(), () -> {
try {
return Files.readString(en.getValue(), StandardCharsets.UTF_8);
return Files.readAllBytes(en.getValue());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
@ -364,7 +359,13 @@ public class ShareClient extends AbstractUIPlugin implements MessageListener, Di
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());
if(sharedProject == null) return null;
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.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.DEBUG_MODE, "Debug mode", getFieldEditorParent())); // TODO: update view on setting change
}
@Override

View File

@ -7,6 +7,7 @@ public class ShareClientPreferences {
USERNAME = "username",
SHOW_CURSORS = "showCursors",
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 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.util.listeners.ShareClientDocumentListener;
import me.mrletsplay.shareclientcore.connection.RemoteConnection;
import me.mrletsplay.shareclientcore.document.DocumentListener;
import me.mrletsplay.shareclientcore.document.SharedDocument;
public class ShareSession {
@ -79,51 +83,48 @@ public class ShareSession {
public SharedDocument getOrCreateSharedDocument(ProjectRelativePath path, Supplier<byte[]> initialContents) {
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() {
//
// @Override
// 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
// Display.getDefault().asyncExec(() -> {
// ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
// if(documentListener != null) {
// IDocument document = documentListener.getDocument();
//
// documentListener.setIgnoreChanges(true);
// try {
// document.replace(index, 0, String.valueOf(character));
// } catch (BadLocationException e) {
// e.printStackTrace();
// // TODO: treat as inconsistency
//// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
// }
// documentListener.setIgnoreChanges(false);
// }
// });
// }
//
// @Override
// public void onDelete(int index) {
// Display.getDefault().asyncExec(() -> {
// ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
// if(documentListener != null) {
// IDocument document = documentListener.getDocument();
//
// documentListener.setIgnoreChanges(true);
// try {
// document.replace(index, 1, "");
// } catch (BadLocationException e) {
// e.printStackTrace();
// // TODO: treat as inconsistency
//// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
// }
// documentListener.setIgnoreChanges(false);
// }
// });
// }
// });
doc.addListener(new DocumentListener() {
@Override
public void onInsert(int index, byte character) {
// TODO: assert that we are on the UI thread
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
if(documentListener != null) {
IDocument document = documentListener.getDocument();
documentListener.setIgnoreChanges(true);
try {
document.replace(index, 0, String.valueOf((char) character)); // FIXME: dangerous! will explode on multi-byte characters
} catch (BadLocationException e) {
e.printStackTrace();
// TODO: treat as inconsistency
// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
}
documentListener.setIgnoreChanges(false);
}
}
@Override
public void onDelete(int index) {
// TODO: assert that we are on the UI thread
ShareClientDocumentListener documentListener = ShareClient.getDefault().getPartListener().getListener(path);
if(documentListener != null) {
IDocument document = documentListener.getDocument();
documentListener.setIgnoreChanges(true);
try {
document.replace(index, 1, "");
} catch (BadLocationException e) {
e.printStackTrace();
// TODO: treat as inconsistency
// MessageDialog.openError(null, "Share Client", "Failed to update document: " + e.toString());
}
documentListener.setIgnoreChanges(false);
}
}
});
return doc;
});

View File

@ -1,9 +1,10 @@
package me.mrletsplay.shareclient.util.listeners;
import java.nio.charset.StandardCharsets;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.swt.widgets.Display;
import me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.util.ProjectAndPath;
@ -38,20 +39,20 @@ public class ShareClientDocumentListener implements IDocumentListener {
System.out.println("UPDATE ON THREAD " + Thread.currentThread());
Display.getDefault().asyncExec(() -> {
ShareSession session = ShareClient.getDefault().getActiveSession();
if(session == null) return;
// FIXME: bug somewhere here, can cause discrepancies in local editor when editing while changes are coming in
SharedProject sharedProject = session.getSharedProject(path.project());
if(sharedProject == null) return;
ShareSession session = ShareClient.getDefault().getActiveSession();
if(session == 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());
}
SharedProject sharedProject = session.getSharedProject(path.project());
if(sharedProject == null) return;
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

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 me.mrletsplay.shareclient.ShareClient;
import me.mrletsplay.shareclient.ShareClientPreferences;
import me.mrletsplay.shareclient.util.ShareSession;
import me.mrletsplay.shareclientcore.connection.ConnectionException;
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) {
toolbars.add(joinSession);
}else {
@ -170,6 +198,10 @@ public class ShareView extends ViewPart {
toolbars.add(showSettings);
if(ShareClient.getDefault().getPreferenceStore().getBoolean(ShareClientPreferences.DEBUG_MODE)) {
toolbars.add(debug);
}
bars.updateActionBars();
}