Refactoring
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / quelaton / DefaultFcpClient.java
1 package net.pterodactylus.fcp.quelaton;
2
3 import java.io.IOException;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.ExecutorService;
6 import java.util.concurrent.Future;
7 import java.util.concurrent.atomic.AtomicBoolean;
8 import java.util.concurrent.atomic.AtomicReference;
9 import java.util.function.Supplier;
10
11 import net.pterodactylus.fcp.ClientHello;
12 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
13 import net.pterodactylus.fcp.FcpConnection;
14 import net.pterodactylus.fcp.FcpKeyPair;
15 import net.pterodactylus.fcp.GenerateSSK;
16 import net.pterodactylus.fcp.NodeHello;
17 import net.pterodactylus.fcp.SSKKeypair;
18
19 /**
20  * Default {@link FcpClient} implementation.
21  *
22  * @author <a href="bombe@freenetproject.org">David ‘Bombe’ Roden</a>
23  */
24 public class DefaultFcpClient implements FcpClient {
25
26         private final ExecutorService threadPool;
27         private final String hostname;
28         private final int port;
29         private final AtomicReference<FcpConnection> fcpConnection = new AtomicReference<>();
30         private final Supplier<String> clientName;
31         private final Supplier<String> expectedVersion;
32
33         public DefaultFcpClient(ExecutorService threadPool, String hostname, int port, Supplier<String> clientName,
34                         Supplier<String> expectedVersion) {
35                 this.threadPool = threadPool;
36                 this.hostname = hostname;
37                 this.port = port;
38                 this.clientName = clientName;
39                 this.expectedVersion = expectedVersion;
40         }
41
42         private void connect() throws IOException {
43                 if (fcpConnection.get() != null) {
44                         return;
45                 }
46                 fcpConnection.compareAndSet(null, createConnection());
47         }
48
49         private FcpConnection createConnection() throws IOException {
50                 FcpConnection connection = new FcpConnection(hostname, port);
51                 connection.connect();
52                 AtomicReference<NodeHello> receivedNodeHello = new AtomicReference<>();
53                 AtomicBoolean receivedClosed = new AtomicBoolean();
54                 FcpReplySequence nodeHelloSequence = new FcpReplySequence(threadPool, connection);
55                 nodeHelloSequence
56                                 .handle(NodeHello.class)
57                                 .with((nodeHello) -> receivedNodeHello.set(nodeHello));
58                 nodeHelloSequence
59                                 .handle(CloseConnectionDuplicateClientName.class)
60                                 .with((closeConnection) -> receivedClosed.set(true));
61                 nodeHelloSequence.waitFor(() -> receivedNodeHello.get() != null || receivedClosed.get());
62                 ClientHello clientHello = new ClientHello(clientName.get(), expectedVersion.get());
63                 try {
64                         nodeHelloSequence.send(clientHello).get();
65                 } catch (InterruptedException | ExecutionException e) {
66                         connection.close();
67                         throw new IOException(String.format("Could not connect to %s:%d.", hostname, port), e);
68                 }
69                 return connection;
70         }
71
72         @Override
73         public GenerateKeypairCommand generateKeypair() {
74                 return new GenerateKeypairCommandImpl();
75         }
76
77         private class GenerateKeypairCommandImpl implements GenerateKeypairCommand {
78
79                 @Override
80                 public Future<FcpKeyPair> execute() {
81                         return threadPool.submit(() -> {
82                                 connect();
83                                 Sequence sequence = new Sequence();
84                                 FcpReplySequence replySequence = new FcpReplySequence(threadPool, fcpConnection.get());
85                                 replySequence.handle(SSKKeypair.class).with(sequence::handleSSKKeypair);
86                                 replySequence.waitFor(sequence::isFinished);
87                                 replySequence.send(new GenerateSSK()).get();
88                                 return sequence.getKeyPair();
89                         });
90                 }
91
92                 private class Sequence {
93
94                         private AtomicReference<FcpKeyPair> keyPair = new AtomicReference<>();
95
96                         public void handleSSKKeypair(SSKKeypair sskKeypair) {
97                                 keyPair.set(new FcpKeyPair(sskKeypair.getRequestURI(), sskKeypair.getInsertURI()));
98                         }
99
100                         public boolean isFinished() {
101                                 return keyPair.get() != null;
102                         }
103
104                         public FcpKeyPair getKeyPair() {
105                                 return keyPair.get();
106                         }
107
108                 }
109
110         }
111
112 }