06d54e0b4ef2f4811eca52b3cc3b85bc445f5642
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / quelaton / ClientPutCommandImpl.java
1 package net.pterodactylus.fcp.quelaton;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.nio.file.Files;
7 import java.util.List;
8 import java.util.Objects;
9 import java.util.Optional;
10 import java.util.concurrent.CopyOnWriteArrayList;
11 import java.util.concurrent.ExecutionException;
12 import java.util.concurrent.ExecutorService;
13 import java.util.concurrent.atomic.AtomicBoolean;
14 import java.util.concurrent.atomic.AtomicLong;
15 import java.util.concurrent.atomic.AtomicReference;
16 import java.util.function.Consumer;
17
18 import net.pterodactylus.fcp.ClientPut;
19 import net.pterodactylus.fcp.FcpMessage;
20 import net.pterodactylus.fcp.Key;
21 import net.pterodactylus.fcp.ProtocolError;
22 import net.pterodactylus.fcp.PutFailed;
23 import net.pterodactylus.fcp.PutSuccessful;
24 import net.pterodactylus.fcp.TestDDAComplete;
25 import net.pterodactylus.fcp.TestDDAReply;
26 import net.pterodactylus.fcp.TestDDARequest;
27 import net.pterodactylus.fcp.TestDDAResponse;
28 import net.pterodactylus.fcp.URIGenerated;
29 import net.pterodactylus.fcp.UploadFrom;
30
31 import com.google.common.util.concurrent.ListenableFuture;
32 import com.google.common.util.concurrent.ListeningExecutorService;
33 import com.google.common.util.concurrent.MoreExecutors;
34
35 /**
36  * Default {@link ClientPutCommand} implemented based on {@link FcpDialog}.
37  *
38  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
39  */
40 class ClientPutCommandImpl implements ClientPutCommand {
41
42         private final ListeningExecutorService threadPool;
43         private final ConnectionSupplier connectionSupplier;
44         private final AtomicReference<String> redirectUri = new AtomicReference<>();
45         private final AtomicReference<File> file = new AtomicReference<>();
46         private final AtomicReference<InputStream> payload = new AtomicReference<>();
47         private final AtomicLong length = new AtomicLong();
48         private final AtomicReference<String> targetFilename = new AtomicReference<>();
49         private final List<Consumer<String>> keyGenerateds = new CopyOnWriteArrayList<>();
50
51         public ClientPutCommandImpl(ExecutorService threadPool, ConnectionSupplier connectionSupplier) {
52                 this.threadPool = MoreExecutors.listeningDecorator(threadPool);
53                 this.connectionSupplier = connectionSupplier;
54         }
55
56         @Override
57         public ClientPutCommand onKeyGenerated(Consumer<String> keyGenerated) {
58                 keyGenerateds.add(keyGenerated);
59                 return this;
60         }
61
62         @Override
63         public ClientPutCommand named(String targetFilename) {
64                 this.targetFilename.set(targetFilename);
65                 return this;
66         }
67
68         @Override
69         public WithUri redirectTo(String uri) {
70                 this.redirectUri.set(Objects.requireNonNull(uri, "uri must not be null"));
71                 return this::key;
72         }
73
74         @Override
75         public WithUri from(File file) {
76                 this.file.set(Objects.requireNonNull(file, "file must not be null"));
77                 return this::key;
78         }
79
80         @Override
81         public WithLength from(InputStream inputStream) {
82                 payload.set(Objects.requireNonNull(inputStream, "inputStream must not be null"));
83                 return this::length;
84         }
85
86         private WithUri length(long length) {
87                 this.length.set(length);
88                 return this::key;
89         }
90
91         private Executable<Optional<Key>> key(String uri) {
92                 return () -> threadPool.submit(() -> execute(uri));
93         }
94
95         private Optional<Key> execute(String uri) throws InterruptedException, ExecutionException, IOException {
96                 String identifier = new RandomIdentifierGenerator().generate();
97                 ClientPut clientPut = createClientPutCommand(uri, identifier);
98                 try (ClientPutDialog clientPutDialog = new ClientPutDialog()) {
99                         return clientPutDialog.send(clientPut).get();
100                 }
101         }
102
103         private ClientPut createClientPutCommand(String uri, String identifier) {
104                 ClientPut clientPut;
105                 if (file.get() != null) {
106                         clientPut = createClientPutFromDisk(uri, identifier, file.get());
107                 } else if (redirectUri.get() != null) {
108                         clientPut = createClientPutRedirect(uri, identifier, redirectUri.get());
109                 } else {
110                         clientPut = createClientPutDirect(uri, identifier, length.get(), payload.get());
111                 }
112                 if (targetFilename.get() != null) {
113                         clientPut.setTargetFilename(targetFilename.get());
114                 }
115                 return clientPut;
116         }
117
118         private ClientPut createClientPutFromDisk(String uri, String identifier, File file) {
119                 ClientPut clientPut = new ClientPut(uri, identifier, UploadFrom.disk);
120                 clientPut.setFilename(file.getAbsolutePath());
121                 return clientPut;
122         }
123
124         private ClientPut createClientPutRedirect(String uri, String identifier, String redirectUri) {
125                 ClientPut clientPut = new ClientPut(uri, identifier, UploadFrom.redirect);
126                 clientPut.setTargetURI(redirectUri);
127                 return clientPut;
128         }
129
130         private ClientPut createClientPutDirect(String uri, String identifier, long length, InputStream payload) {
131                 ClientPut clientPut = new ClientPut(uri, identifier, UploadFrom.direct);
132                 clientPut.setDataLength(length);
133                 clientPut.setPayloadInputStream(payload);
134                 return clientPut;
135         }
136
137         private class ClientPutDialog extends FcpDialog<Optional<Key>> {
138
139                 private final AtomicReference<FcpMessage> originalClientPut = new AtomicReference<>();
140                 private final AtomicReference<String> directory = new AtomicReference<>();
141                 private final AtomicReference<Key> finalKey = new AtomicReference<>();
142                 private final AtomicBoolean putFinished = new AtomicBoolean();
143
144                 public ClientPutDialog() throws IOException {
145                         super(ClientPutCommandImpl.this.threadPool, ClientPutCommandImpl.this.connectionSupplier.get());
146                 }
147
148                 @Override
149                 protected boolean isFinished() {
150                         return putFinished.get();
151                 }
152
153                 @Override
154                 protected Optional<Key> getResult() {
155                         return Optional.ofNullable(finalKey.get());
156                 }
157
158                 @Override
159                 public ListenableFuture<Optional<Key>> send(FcpMessage fcpMessage) throws IOException {
160                         originalClientPut.set(fcpMessage);
161                         String filename = fcpMessage.getField("Filename");
162                         if (filename != null) {
163                                 directory.set(new File(filename).getParent());
164                         }
165                         return super.send(fcpMessage);
166                 }
167
168                 @Override
169                 protected void consumeURIGenerated(URIGenerated uriGenerated) {
170                         for (Consumer<String> keyGenerated : keyGenerateds) {
171                                 keyGenerated.accept(uriGenerated.getURI());
172                         }
173                 }
174
175                 @Override
176                 protected void consumePutSuccessful(PutSuccessful putSuccessful) {
177                         finalKey.set(new Key(putSuccessful.getURI()));
178                         putFinished.set(true);
179                 }
180
181                 @Override
182                 protected void consumePutFailed(PutFailed putFailed) {
183                         putFinished.set(true);
184                 }
185
186                 @Override
187                 protected void consumeProtocolError(ProtocolError protocolError) {
188                         if (protocolError.getCode() == 25) {
189                                 setIdentifier(directory.get());
190                                 sendMessage(new TestDDARequest(directory.get(), true, false));
191                         } else {
192                                 putFinished.set(true);
193                         }
194                 }
195
196                 @Override
197                 protected void consumeTestDDAReply(TestDDAReply testDDAReply) {
198                         try {
199                                 String readContent = Files.readAllLines(new File(testDDAReply.getReadFilename()).toPath()).get(0);
200                                 sendMessage(new TestDDAResponse(directory.get(), readContent));
201                         } catch (IOException e) {
202                                 sendMessage(new TestDDAResponse(directory.get(), "failed-to-read"));
203                         }
204                 }
205
206                 @Override
207                 protected void consumeTestDDAComplete(TestDDAComplete testDDAComplete) {
208                         setIdentifier(originalClientPut.get().getField("Identifier"));
209                         sendMessage(originalClientPut.get());
210                 }
211
212         }
213
214 }