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