Add command that loads a plugin
authorDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Fri, 21 Aug 2015 05:10:11 +0000 (07:10 +0200)
committerDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Fri, 21 Aug 2015 05:10:11 +0000 (07:10 +0200)
src/main/java/net/pterodactylus/fcp/LoadPlugin.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/quelaton/DefaultFcpClient.java
src/main/java/net/pterodactylus/fcp/quelaton/FcpClient.java
src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommand.java [new file with mode: 0644]
src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommandImpl.java [new file with mode: 0644]
src/test/java/net/pterodactylus/fcp/quelaton/DefaultFcpClientTest.java

diff --git a/src/main/java/net/pterodactylus/fcp/LoadPlugin.java b/src/main/java/net/pterodactylus/fcp/LoadPlugin.java
new file mode 100644 (file)
index 0000000..9eb2d2d
--- /dev/null
@@ -0,0 +1,47 @@
+package net.pterodactylus.fcp;
+
+/**
+ * The “LoadPlugin” message is used to load a plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class LoadPlugin extends FcpMessage {
+
+       public enum UrlType {
+
+               OFFICIAL,
+               FILE,
+               FREENET,
+               URL
+
+       }
+
+       public enum OfficialSource {
+
+               FREENET,
+               HTTPS
+
+       }
+
+       public LoadPlugin(String identifier) {
+               super("LoadPlugin");
+               setField("Identifier", identifier);
+       }
+
+       public void setPluginUrl(String pluginUrl) {
+               setField("PluginURL", pluginUrl);
+       }
+
+       public void setUrlType(UrlType urlType) {
+               setField("URLType", urlType.toString().toLowerCase());
+       }
+
+       public void setStore(boolean store) {
+               setField("Store", String.valueOf(store));
+       }
+
+       public void setOfficialSource(OfficialSource officialSource) {
+               setField("OfficialSource", officialSource.toString().toLowerCase());
+       }
+
+}
index 4cc7328..be26779 100644 (file)
@@ -114,5 +114,10 @@ public class DefaultFcpClient implements FcpClient {
                return new ModifyPeerNoteCommandImpl(threadPool, this::connect);
        }
 
+       @Override
+       public LoadPluginCommand loadPlugin() {
+               return new LoadPluginCommandImpl(threadPool, this::connect);
+       }
+
 }
 
index ab25d61..5b3b2b1 100644 (file)
@@ -24,4 +24,6 @@ public interface FcpClient {
        ListPeerNotesCommand listPeerNotes();
        ModifyPeerNoteCommand modifyPeerNote();
 
+       LoadPluginCommand loadPlugin();
+
 }
diff --git a/src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommand.java b/src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommand.java
new file mode 100644 (file)
index 0000000..16686a4
--- /dev/null
@@ -0,0 +1,17 @@
+package net.pterodactylus.fcp.quelaton;
+
+import java.util.Optional;
+
+import net.pterodactylus.fcp.PluginInfo;
+
+/**
+ * Loads a plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface LoadPluginCommand {
+
+       LoadPluginCommand addToConfig();
+       Executable<Optional<PluginInfo>> officialFromFreenet(String pluginIdentifier);
+
+}
diff --git a/src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommandImpl.java b/src/main/java/net/pterodactylus/fcp/quelaton/LoadPluginCommandImpl.java
new file mode 100644 (file)
index 0000000..3109e8a
--- /dev/null
@@ -0,0 +1,93 @@
+package net.pterodactylus.fcp.quelaton;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import net.pterodactylus.fcp.LoadPlugin;
+import net.pterodactylus.fcp.LoadPlugin.OfficialSource;
+import net.pterodactylus.fcp.LoadPlugin.UrlType;
+import net.pterodactylus.fcp.PluginInfo;
+import net.pterodactylus.fcp.ProtocolError;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * Default {@link LoadPluginCommand} implementation based on {@link FcpDialog}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class LoadPluginCommandImpl implements LoadPluginCommand {
+
+       private static final RandomIdentifierGenerator IDENTIFIER = new RandomIdentifierGenerator();
+       private final ListeningExecutorService threadPool;
+       private final ConnectionSupplier connectionSupplier;
+       private final LoadPlugin loadPlugin = new LoadPlugin(IDENTIFIER.generate());
+
+       public LoadPluginCommandImpl(ExecutorService threadPool, ConnectionSupplier connectionSupplier) {
+               this.threadPool = MoreExecutors.listeningDecorator(threadPool);
+               this.connectionSupplier = connectionSupplier;
+       }
+
+       @Override
+       public LoadPluginCommand addToConfig() {
+               loadPlugin.setStore(true);
+               return this;
+       }
+
+       @Override
+       public Executable<Optional<PluginInfo>> officialFromFreenet(String pluginIdentifier) {
+               loadPlugin.setUrlType(UrlType.OFFICIAL);
+               loadPlugin.setOfficialSource(OfficialSource.FREENET);
+               loadPlugin.setPluginUrl(pluginIdentifier);
+               return this::execute;
+       }
+
+       private ListenableFuture<Optional<PluginInfo>> execute() {
+               return threadPool.submit(this::executeDialog);
+       }
+
+       private Optional<PluginInfo> executeDialog() throws IOException, ExecutionException, InterruptedException {
+               try (LoadPluginDialog loadPluginDialog = new LoadPluginDialog()) {
+                       return loadPluginDialog.send(loadPlugin).get();
+               }
+       }
+
+       private class LoadPluginDialog extends FcpDialog<Optional<PluginInfo>> {
+
+               private final AtomicBoolean finished = new AtomicBoolean();
+               private final AtomicReference<PluginInfo> pluginInfo = new AtomicReference<>();
+
+               public LoadPluginDialog() throws IOException {
+                       super(threadPool, connectionSupplier.get());
+               }
+
+               @Override
+               protected boolean isFinished() {
+                       return finished.get();
+               }
+
+               @Override
+               protected Optional<PluginInfo> getResult() {
+                       return Optional.ofNullable(pluginInfo.get());
+               }
+
+               @Override
+               protected void consumePluginInfo(PluginInfo pluginInfo) {
+                       this.pluginInfo.set(pluginInfo);
+                       finished.set(true);
+               }
+
+               @Override
+               protected void consumeProtocolError(ProtocolError protocolError) {
+                       finished.set(true);
+               }
+
+       }
+
+}
index 35a2857..77e3588 100644 (file)
@@ -33,6 +33,7 @@ import net.pterodactylus.fcp.NodeData;
 import net.pterodactylus.fcp.NodeRef;
 import net.pterodactylus.fcp.Peer;
 import net.pterodactylus.fcp.PeerNote;
+import net.pterodactylus.fcp.PluginInfo;
 import net.pterodactylus.fcp.Priority;
 import net.pterodactylus.fcp.fake.FakeTcpServer;
 import net.pterodactylus.fcp.quelaton.ClientGetCommand.Data;
@@ -1986,4 +1987,90 @@ public class DefaultFcpClientTest {
                assertThat(newConfigData.get().getCurrent("foo.bar"), is("baz"));
        }
 
+       @Test
+       public void defaultFcpClientCanLoadPluginFromFreenet() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "LoadPlugin",
+                       "Identifier=" + identifier,
+                       "PluginURL=superPlugin",
+                       "URLType=official",
+                       "OfficialSource=freenet",
+                       "EndMessage"
+               ));
+               assertThat(lines, not(contains(startsWith("Store="))));
+               fcpServer.writeLine(
+                       "PluginInfo",
+                       "Identifier=" + identifier,
+                       "PluginName=superPlugin",
+                       "IsTalkable=true",
+                       "LongVersion=1.2.3",
+                       "Version=42",
+                       "OriginUri=superPlugin",
+                       "Started=true",
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().isTalkable(), is(true));
+               assertThat(pluginInfo.get().get().getVersion(), is("42"));
+               assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
+               assertThat(pluginInfo.get().get().isStarted(), is(true));
+       }
+
+       @Test
+       public void defaultFcpClientCanLoadPersistentPluginFromFreenet() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo =
+                       fcpClient.loadPlugin().addToConfig().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               assertThat(lines, matchesFcpMessage(
+                       "LoadPlugin",
+                       "Identifier=" + identifier,
+                       "PluginURL=superPlugin",
+                       "URLType=official",
+                       "OfficialSource=freenet",
+                       "Store=true",
+                       "EndMessage"
+               ));
+               fcpServer.writeLine(
+                       "PluginInfo",
+                       "Identifier=" + identifier,
+                       "PluginName=superPlugin",
+                       "IsTalkable=true",
+                       "LongVersion=1.2.3",
+                       "Version=42",
+                       "OriginUri=superPlugin",
+                       "Started=true",
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().get().getPluginName(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().getOriginalURI(), is("superPlugin"));
+               assertThat(pluginInfo.get().get().isTalkable(), is(true));
+               assertThat(pluginInfo.get().get().getVersion(), is("42"));
+               assertThat(pluginInfo.get().get().getLongVersion(), is("1.2.3"));
+               assertThat(pluginInfo.get().get().isStarted(), is(true));
+       }
+
+       @Test
+       public void failedLoadingPluginIsRecognized() throws ExecutionException, InterruptedException,
+       IOException {
+               Future<Optional<PluginInfo>> pluginInfo = fcpClient.loadPlugin().officialFromFreenet("superPlugin").execute();
+               connectNode();
+               List<String> lines = fcpServer.collectUntil(is("EndMessage"));
+               String identifier = extractIdentifier(lines);
+               fcpServer.writeLine(
+                       "ProtocolError",
+                       "Identifier=" + identifier,
+                       "EndMessage"
+               );
+               assertThat(pluginInfo.get().isPresent(), is(false));
+       }
+
 }