Skip to content

Commit 6cc9fb6

Browse files
committed
autoreloading and live editing for couchbase documents
1 parent c9a83fe commit 6cc9fb6

File tree

6 files changed

+176
-8
lines changed

6 files changed

+176
-8
lines changed

src/main/java/com/couchbase/intellij/database/DataLoader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ public static Set<String> listBucketNames(String clusterUrl, boolean ssl, String
554554

555555
}
556556

557-
public static SavedCluster saveDatabaseCredentials(String name, String url, String queryParams, boolean isSSL, String username, String password, String defaultBucket, Boolean ldap) {
557+
public static SavedCluster saveDatabaseCredentials(String name, String url, String queryParams, boolean isSSL, String username, String password, String defaultBucket, Boolean ldap, Set<String> options) {
558558
String key = username + ":" + name;
559559
SavedCluster sc = new SavedCluster();
560560
sc.setId(key);
@@ -565,6 +565,7 @@ public static SavedCluster saveDatabaseCredentials(String name, String url, Stri
565565
sc.setUrl(adjustClusterProtocol(url, isSSL));
566566
sc.setDefaultBucket(defaultBucket);
567567
sc.setLDAP(ldap);
568+
sc.options(options);
568569

569570
Clusters clusters = ClustersStorage.getInstance().getValue();
570571

src/main/java/com/couchbase/intellij/persistence/Clusters.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.couchbase.intellij.persistence;
22

33
import java.util.HashMap;
4+
import java.util.HashSet;
45
import java.util.Map;
56

67
public class Clusters {
@@ -42,6 +43,21 @@ public Map<String, Map<String, Long>> getInferCacheUpdateTimes() {
4243
public void setInferCacheUpdateTimes(Map<String, Map<String, Long>> inferCacheUpdateTimes) {
4344
this.inferCacheUpdateTimes = inferCacheUpdateTimes;
4445
}
46+
47+
public static enum Options {
48+
LIVE_POLLING,
49+
LIVE_RELOAD;
50+
public static HashSet<String> defaults() {
51+
HashSet<String> set = new HashSet<>();
52+
set.add(LIVE_POLLING.toString());
53+
return set;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return name().toLowerCase();
59+
}
60+
}
4561
}
4662

4763

src/main/java/com/couchbase/intellij/persistence/CouchbaseDocumentVirtualFile.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class CouchbaseDocumentVirtualFile extends VirtualFile {
4343
private final String name;
4444
private long size;
4545
private FileType type;
46+
private boolean desynchronized = false;
4647

4748
private final Project project;
4849

@@ -206,6 +207,7 @@ public void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable
206207
}
207208

208209
private void updateFromCluster(byte[] newContent) {
210+
desynchronized = false;
209211
int newContentHash = Arrays.hashCode(newContent);
210212
if (this.contentHash != newContentHash) {
211213
boolean first = this.mtime == 0;
@@ -249,4 +251,17 @@ void setContentHash(int hash) {
249251
void setSize(long length) {
250252
this.size = length;
251253
}
254+
255+
public boolean checkUpdatedOnCluster() {
256+
GetResult gr = fetch();
257+
return contentHash != Arrays.hashCode(fetchContent(gr));
258+
}
259+
260+
public void setDesynchronized(boolean desynchronized) {
261+
this.desynchronized = desynchronized;
262+
}
263+
264+
public boolean isDesynchronized() {
265+
return desynchronized;
266+
}
252267
}

src/main/java/com/couchbase/intellij/persistence/SavedCluster.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import com.couchbase.client.java.json.JsonObject;
44
import com.couchbase.intellij.persistence.storage.ClustersStorage;
55

6-
import java.util.Base64;
7-
import java.util.HashMap;
8-
import java.util.Map;
6+
import java.util.*;
97
import java.util.concurrent.TimeUnit;
8+
import java.util.stream.Collectors;
9+
import java.util.stream.Stream;
1010

1111
public class SavedCluster {
1212
private String id;
@@ -28,6 +28,7 @@ public class SavedCluster {
2828
private Long inferCachePeriod;
2929

3030
private QueryPreferences queryPreferences;
31+
private String options;
3132

3233
public Boolean getLDAP() {
3334
if (ldap == null) {
@@ -109,6 +110,7 @@ public String toString() {
109110
", username='" + username + '\'' +
110111
", sslEnable=" + sslEnable +
111112
", defaultBucket='" + defaultBucket + '\'' +
113+
", options='" + + '\'' +
112114
'}';
113115
}
114116

@@ -209,4 +211,45 @@ public QueryPreferences getQueryPreferences() {
209211
public void setQueryPreferences(QueryPreferences queryPreferences) {
210212
this.queryPreferences = queryPreferences;
211213
}
214+
215+
public void addOption(String option) {
216+
options = Stream.concat(options(), Stream.of(option)).collect(Collectors.joining(","));
217+
}
218+
219+
public void removeOption(String option) {
220+
if (this.options != null) {
221+
options = options().filter(o -> !o.equals(option)).collect(Collectors.joining(","));
222+
}
223+
}
224+
225+
public String getOptions() {
226+
return options;
227+
}
228+
229+
public void setOptions(String options) {
230+
this.options = options;
231+
}
232+
233+
public void options(Set<String> data) {
234+
options = data.stream().collect(Collectors.joining(","));
235+
}
236+
237+
public Stream<String> options() {
238+
if (options == null) {
239+
return Stream.empty();
240+
}
241+
return Arrays.stream(options.split(","));
242+
}
243+
244+
public boolean hasOption(Clusters.Options option) {
245+
return options().anyMatch(o -> option.name().toLowerCase().equals(o));
246+
}
247+
248+
public void addOption(Clusters.Options option) {
249+
addOption(option.name().toLowerCase());
250+
}
251+
252+
public void removeOption(Clusters.Options option) {
253+
removeOption(option.name().toLowerCase());
254+
}
212255
}

src/main/java/com/couchbase/intellij/tree/CouchbaseDocumentEditorRefresher.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package com.couchbase.intellij.tree;
22

3+
import com.couchbase.client.java.Cluster;
4+
import com.couchbase.client.java.env.ClusterEnvironment;
5+
import com.couchbase.intellij.database.ActiveCluster;
6+
import com.couchbase.intellij.persistence.Clusters;
7+
import com.couchbase.intellij.persistence.CouchbaseDocumentVirtualFile;
8+
import com.couchbase.intellij.persistence.SavedCluster;
39
import com.couchbase.intellij.workbench.Log;
410
import com.intellij.openapi.application.ApplicationManager;
511
import com.intellij.openapi.fileEditor.FileDocumentManager;
612
import com.intellij.openapi.fileEditor.FileEditor;
13+
import com.intellij.openapi.ui.Messages;
14+
import com.intellij.openapi.vfs.VirtualFile;
715
import com.intellij.openapi.vfs.VirtualFileManager;
816
import com.intellij.psi.FileViewProvider;
917
import com.intellij.psi.PsiManager;
@@ -24,9 +32,29 @@ public static void attach(FileEditor editor) {
2432

2533

2634
private void queueReload() {
27-
FileDocumentManager documentManager = FileDocumentManager.getInstance();
28-
if (!documentManager.isFileModified(editor.getFile())) {
29-
documentManager.reloadFiles(editor.getFile());
35+
SavedCluster cluster = ActiveCluster.getInstance().getSavedCluster();
36+
if (cluster != null && cluster.hasOption(Clusters.Options.LIVE_POLLING)) {
37+
FileDocumentManager documentManager = FileDocumentManager.getInstance();
38+
VirtualFile file = editor.getFile();
39+
if (file instanceof CouchbaseDocumentVirtualFile) {
40+
if (cluster.hasOption(Clusters.Options.LIVE_RELOAD)) {
41+
documentManager.reloadFiles(file);
42+
} else {
43+
CouchbaseDocumentVirtualFile cbfile = (CouchbaseDocumentVirtualFile) file;
44+
if (!cbfile.isDesynchronized() && cbfile.checkUpdatedOnCluster()) {
45+
String[] options = {"Replace document with server's version", "Keep current document"};
46+
int result = Messages.showDialog(
47+
String.format("Document `%s` was modified in the server. How would you like to proceed?", file.getName()), "Document Conflict"
48+
, options, 0, Messages.getWarningIcon());
49+
50+
if (result == 0) {
51+
documentManager.reloadFiles(file);
52+
} else {
53+
cbfile.setDesynchronized(true);
54+
}
55+
}
56+
}
57+
}
3058
}
3159
}
3260

src/main/java/com/couchbase/intellij/tree/NewConnectionDialog.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import com.couchbase.intellij.database.DataLoader;
55
import com.couchbase.intellij.persistence.ClusterAlreadyExistsException;
6+
import com.couchbase.intellij.persistence.Clusters;
67
import com.couchbase.intellij.persistence.DuplicatedClusterNameAndUserException;
78
import com.couchbase.intellij.persistence.SavedCluster;
89
import com.couchbase.intellij.tools.dialog.CollapsiblePanel;
@@ -31,8 +32,10 @@
3132
import java.io.IOException;
3233
import java.net.URISyntaxException;
3334
import java.util.ArrayList;
35+
import java.util.HashSet;
3436
import java.util.List;
3537
import java.util.Set;
38+
import java.util.stream.Collectors;
3639

3740
public class NewConnectionDialog extends DialogWrapper {
3841

@@ -56,14 +59,21 @@ public class NewConnectionDialog extends DialogWrapper {
5659
private JBTextField apiKeyField;
5760
private JPasswordField apiSecretField;
5861
private JPanel wrapperPanel;
62+
private JCheckBox liveReload;
5963

6064
private DefaultMutableTreeNode clickedNode;
65+
private Set<String> options;
6166

6267
public NewConnectionDialog(Project project, Tree tree, SavedCluster savedCluster, DefaultMutableTreeNode clickedNode) {
6368
super(false);
6469
this.tree = tree;
6570
this.project = project;
6671
this.savedCluster = savedCluster;
72+
if (savedCluster != null) {
73+
options = savedCluster.options().collect(Collectors.toSet());
74+
} else {
75+
options = Clusters.Options.defaults();
76+
}
6777
this.clickedNode = clickedNode;
6878
createUIComponents();
6979

@@ -177,7 +187,7 @@ private void handleSaveConnection() {
177187
getBaseUrl(hostTextField.getText()), getQueryParams(hostTextField.getText()),
178188
enableSSLCheckBox.isSelected(), usernameTextField.getText(), String.valueOf(passwordField.getPassword()),
179189
defaultBucketTextField.getText().trim().isEmpty() ? null : defaultBucketTextField.getText(),
180-
ldapAuthCheckbox.isSelected());
190+
ldapAuthCheckbox.isSelected(), options);
181191
messageLabel.setText("Connection was successful");
182192
TreeActionHandler.connectToCluster(project, sc, tree, null);
183193
close(DialogWrapper.CANCEL_EXIT_CODE);
@@ -568,12 +578,67 @@ public Class<?> getColumnClass(int columnIndex) {
568578
thirdPanel.setBorder(JBUI.Borders.empty(10));
569579
thirdPanel.add(topThirdPanel, BorderLayout.NORTH);
570580
thirdPanel.add(consoleScrollPane, BorderLayout.CENTER);
581+
tabbedPane.addTab("Connection", createAdvancedConnectionPanel());
571582
tabbedPane.addTab("Troubleshooting", thirdPanel);
572583
tabbedPane.setBorder(JBUI.Borders.emptyTop(10));
573584

574585
return tabbedPane;
575586
}
576587

588+
private Component createAdvancedConnectionPanel() {
589+
ScrollPane sp = new ScrollPane();
590+
JBPanel panel = new JBPanel(new GridBagLayout());
591+
592+
JBCheckBox liveEditing = new JBCheckBox("Keep opened documents synchronized");
593+
liveEditing.setToolTipText("Requires document polling to be enabled. If selected, changes to Couchbase documents opened in IDE will be " +
594+
"auto-saved onto the cluster and vice-versa.\n" +
595+
"If not selected, then editing a document that got changed " +
596+
"on the cluster will trigger an alert.");
597+
if (options.contains("live_polling")) {
598+
liveEditing.setSelected(options.contains("live_reload"));
599+
} else {
600+
liveEditing.setEnabled(false);
601+
}
602+
liveEditing.addChangeListener(e -> {
603+
if (liveEditing.isSelected()) {
604+
options.add("live_reload");
605+
} else {
606+
options.remove("live_reload");
607+
}
608+
});
609+
610+
JBCheckBox documentPolling = new JBCheckBox("Poll cluster for document changes");
611+
documentPolling.setToolTipText("If selected, the plugin will periodically poll the cluster for changes in opened documents.");
612+
documentPolling.setSelected(options.contains("live_polling"));
613+
documentPolling.addChangeListener(e -> {
614+
if (documentPolling.isSelected()) {
615+
options.add("live_polling");
616+
} else {
617+
options.remove("live_polling");
618+
options.remove("live_reload");
619+
liveEditing.setSelected(false);
620+
}
621+
liveEditing.setEnabled(documentPolling.isSelected());
622+
});
623+
624+
625+
GridBagConstraints gbc = new GridBagConstraints();
626+
gbc.insets = JBUI.insets(5);
627+
gbc.fill = GridBagConstraints.BOTH;
628+
gbc.gridwidth = GridBagConstraints.REMAINDER;
629+
gbc.weightx = 1;
630+
gbc.weighty = 0;
631+
gbc.anchor = GridBagConstraints.NORTHWEST;
632+
panel.add(documentPolling, gbc);
633+
gbc.gridx++;
634+
panel.add(liveEditing, gbc);
635+
JPanel tbWrapPanel = new JPanel(new BorderLayout());
636+
tbWrapPanel.setPreferredSize(new Dimension(400, 80));
637+
tbWrapPanel.add(panel, BorderLayout.NORTH);
638+
sp.add(tbWrapPanel);
639+
return sp;
640+
}
641+
577642
private JPanel createCouchbaseBanner() {
578643
JPanel leftPanel = new JPanel();
579644
leftPanel.setBackground(Color.WHITE);

0 commit comments

Comments
 (0)