ssl -> cryptutils
[fanfix.git] / src / be / nikiroo / fanfix / library / RemoteLibrary.java
CommitLineData
e42573a0 1package be.nikiroo.fanfix.library;
b0e88ebd
NR
2
3import java.io.File;
4import java.io.IOException;
edf79e5e 5import java.net.URL;
e6249b0f 6import java.net.UnknownHostException;
68e2c6d2
NR
7import java.util.ArrayList;
8import java.util.List;
b0e88ebd 9
e42573a0 10import be.nikiroo.fanfix.Instance;
b0e88ebd
NR
11import be.nikiroo.fanfix.data.MetaData;
12import be.nikiroo.fanfix.data.Story;
16a81ef7 13import be.nikiroo.utils.Image;
b0e88ebd 14import be.nikiroo.utils.Progress;
416c54f8 15import be.nikiroo.utils.StringUtils;
b0e88ebd 16import be.nikiroo.utils.Version;
62c63b07 17import be.nikiroo.utils.serial.server.ConnectActionClientObject;
b0e88ebd 18
68e2c6d2
NR
19/**
20 * This {@link BasicLibrary} will access a remote server to list the available
a85e8077 21 * stories, and download the ones you try to load to the local directory
68e2c6d2
NR
22 * specified in the configuration.
23 *
24 * @author niki
25 */
26public class RemoteLibrary extends BasicLibrary {
b0e88ebd
NR
27 private String host;
28 private int port;
ea734ab4 29 private final String key;
68e2c6d2
NR
30
31 /**
32 * Create a {@link RemoteLibrary} linked to the given server.
33 *
2070ced5
NR
34 * @param key
35 * the key that will allow us to exchange information with the
36 * server
68e2c6d2
NR
37 * @param host
38 * the host to contact or NULL for localhost
39 * @param port
40 * the port to contact it on
41 */
2070ced5 42 public RemoteLibrary(String key, String host, int port) {
ea734ab4 43 this.key = key;
b0e88ebd
NR
44 this.host = host;
45 this.port = port;
b0e88ebd
NR
46 }
47
99ccbdf6
NR
48 @Override
49 public String getLibraryName() {
50 return host + ":" + port;
51 }
52
e6249b0f
NR
53 @Override
54 public Status getStatus() {
55 final Status[] result = new Status[1];
56
57 result[0] = Status.INVALID;
58
e6249b0f 59 try {
41029926 60 Instance.getTraceHandler().trace("Getting remote lib status...");
3040c4f0 61 new ConnectActionClientObject(host, port, key) {
e6249b0f
NR
62 @Override
63 public void action(Version serverVersion) throws Exception {
ea734ab4
N
64 try {
65 Object rep = sendCmd(this, new Object[] { "PING" });
66
67 if ("PONG".equals(rep)) {
68 result[0] = Status.READY;
69 } else {
70 result[0] = Status.UNAUTORIZED;
71 }
72 } catch (IllegalArgumentException e) {
e6249b0f
NR
73 result[0] = Status.UNAUTORIZED;
74 }
75 }
76
77 @Override
78 protected void onError(Exception e) {
79 result[0] = Status.UNAVAILABLE;
80 }
ea734ab4 81 }.connect();
e6249b0f
NR
82 } catch (UnknownHostException e) {
83 result[0] = Status.INVALID;
84 } catch (IllegalArgumentException e) {
85 result[0] = Status.INVALID;
86 } catch (Exception e) {
87 result[0] = Status.UNAVAILABLE;
88 }
89
41029926 90 Instance.getTraceHandler().trace("Remote lib status: " + result[0]);
e6249b0f
NR
91 return result[0];
92 }
93
b0e88ebd 94 @Override
16a81ef7
NR
95 public Image getCover(final String luid) {
96 final Image[] result = new Image[1];
b0e88ebd 97
b9ce9cad 98 try {
3040c4f0 99 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
100 @Override
101 public void action(Version serverVersion) throws Exception {
ea734ab4
N
102 Object rep = sendCmd(this,
103 new Object[] { "GET_COVER", luid });
16a81ef7 104 result[0] = (Image) rep;
b9ce9cad 105 }
b0e88ebd 106
b9ce9cad
NR
107 @Override
108 protected void onError(Exception e) {
109 Instance.getTraceHandler().error(e);
110 }
111 }.connect();
112 } catch (Exception e) {
113 Instance.getTraceHandler().error(e);
114 }
b0e88ebd 115
b9ce9cad 116 return result[0];
085a2f9a
NR
117 }
118
119 @Override
e1de8087 120 public Image getCustomSourceCover(final String source) {
3989dfc5
NR
121 return getCustomCover(source, "SOURCE");
122 }
123
124 @Override
125 public Image getCustomAuthorCover(final String author) {
126 return getCustomCover(author, "AUTHOR");
127 }
128
129 // type: "SOURCE" or "AUTHOR"
130 private Image getCustomCover(final String source, final String type) {
16a81ef7 131 final Image[] result = new Image[1];
b9ce9cad
NR
132
133 try {
3040c4f0 134 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
135 @Override
136 public void action(Version serverVersion) throws Exception {
27eba894
NR
137 Object rep = sendCmd(this, new Object[] {
138 "GET_CUSTOM_COVER", type, source });
16a81ef7 139 result[0] = (Image) rep;
b9ce9cad
NR
140 }
141
142 @Override
143 protected void onError(Exception e) {
144 Instance.getTraceHandler().error(e);
145 }
146 }.connect();
147 } catch (Exception e) {
148 Instance.getTraceHandler().error(e);
149 }
150
151 return result[0];
b0e88ebd 152 }
68e2c6d2
NR
153
154 @Override
ff05b828 155 public synchronized Story getStory(final String luid, Progress pg) {
b9ce9cad
NR
156 final Progress pgF = pg;
157 final Story[] result = new Story[1];
68e2c6d2 158
b9ce9cad 159 try {
3040c4f0 160 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
161 @Override
162 public void action(Version serverVersion) throws Exception {
163 Progress pg = pgF;
164 if (pg == null) {
165 pg = new Progress();
166 }
167
ea734ab4
N
168 Object rep = sendCmd(this,
169 new Object[] { "GET_STORY", luid });
b9ce9cad
NR
170
171 MetaData meta = null;
172 if (rep instanceof MetaData) {
173 meta = (MetaData) rep;
174 if (meta.getWords() <= Integer.MAX_VALUE) {
175 pg.setMinMax(0, (int) meta.getWords());
176 }
177 }
178
179 List<Object> list = new ArrayList<Object>();
27eba894 180 for (Object obj = send(null); obj != null; obj = send(null)) {
b9ce9cad
NR
181 list.add(obj);
182 pg.add(1);
183 }
184
185 result[0] = RemoteLibraryServer.rebuildStory(list);
186 pg.done();
187 }
188
189 @Override
190 protected void onError(Exception e) {
191 Instance.getTraceHandler().error(e);
192 }
193 }.connect();
194 } catch (Exception e) {
195 Instance.getTraceHandler().error(e);
196 }
197
198 return result[0];
68e2c6d2
NR
199 }
200
201 @Override
b9ce9cad
NR
202 public synchronized Story save(final Story story, final String luid,
203 Progress pg) throws IOException {
0fa0fe95
NR
204 final String[] luidSaved = new String[1];
205 Progress pgSave = new Progress();
206 Progress pgRefresh = new Progress();
207 if (pg == null) {
208 pg = new Progress();
209 }
210
211 pg.setMinMax(0, 10);
212 pg.addProgress(pgSave, 9);
213 pg.addProgress(pgRefresh, 1);
214
215 final Progress pgF = pgSave;
b9ce9cad 216
3040c4f0 217 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
218 @Override
219 public void action(Version serverVersion) throws Exception {
220 Progress pg = pgF;
b9ce9cad
NR
221 if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
222 pg.setMinMax(0, (int) story.getMeta().getWords());
223 }
224
ea734ab4 225 sendCmd(this, new Object[] { "SAVE_STORY", luid });
b9ce9cad
NR
226
227 List<Object> list = RemoteLibraryServer.breakStory(story);
228 for (Object obj : list) {
229 send(obj);
230 pg.add(1);
231 }
232
edf79e5e 233 luidSaved[0] = (String) send(null);
0fa0fe95 234
b9ce9cad
NR
235 pg.done();
236 }
237
238 @Override
239 protected void onError(Exception e) {
240 Instance.getTraceHandler().error(e);
241 }
242 }.connect();
085a2f9a
NR
243
244 // because the meta changed:
edf79e5e 245 MetaData meta = getInfo(luidSaved[0]);
efa3c511
NR
246 if (story.getMeta().getClass() != null) {
247 // If already available locally:
248 meta.setCover(story.getMeta().getCover());
249 } else {
250 // If required:
251 meta.setCover(getCover(meta.getLuid()));
252 }
edf79e5e 253 story.setMeta(meta);
0fa0fe95
NR
254
255 pg.done();
085a2f9a
NR
256
257 return story;
68e2c6d2
NR
258 }
259
260 @Override
b9ce9cad 261 public synchronized void delete(final String luid) throws IOException {
3040c4f0 262 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
263 @Override
264 public void action(Version serverVersion) throws Exception {
ea734ab4 265 sendCmd(this, new Object[] { "DELETE_STORY", luid });
b9ce9cad
NR
266 }
267
268 @Override
269 protected void onError(Exception e) {
270 Instance.getTraceHandler().error(e);
271 }
272 }.connect();
68e2c6d2
NR
273 }
274
275 @Override
b9ce9cad 276 public void setSourceCover(final String source, final String luid) {
3989dfc5
NR
277 setCover(source, luid, "SOURCE");
278 }
279
280 @Override
281 public void setAuthorCover(final String author, final String luid) {
282 setCover(author, luid, "AUTHOR");
283 }
284
285 // type = "SOURCE" | "AUTHOR"
286 private void setCover(final String value, final String luid,
287 final String type) {
b9ce9cad 288 try {
3040c4f0 289 new ConnectActionClientObject(host, port, key) {
b9ce9cad
NR
290 @Override
291 public void action(Version serverVersion) throws Exception {
ea734ab4
N
292 sendCmd(this,
293 new Object[] { "SET_COVER", type, value, luid });
b9ce9cad
NR
294 }
295
296 @Override
297 protected void onError(Exception e) {
298 Instance.getTraceHandler().error(e);
299 }
300 }.connect();
301 } catch (IOException e) {
302 Instance.getTraceHandler().error(e);
edf79e5e
NR
303 }
304 }
305
306 @Override
307 // Could work (more slowly) without it
308 public Story imprt(final URL url, Progress pg) throws IOException {
00f6344a
NR
309 // Import the file locally if it is actually a file
310 if (url == null || url.getProtocol().equalsIgnoreCase("file")) {
311 return super.imprt(url, pg);
312 }
313
314 // Import it remotely if it is an URL
315
edf79e5e
NR
316 if (pg == null) {
317 pg = new Progress();
318 }
319
320 pg.setMinMax(0, 2);
321 Progress pgImprt = new Progress();
322 Progress pgGet = new Progress();
323 pg.addProgress(pgImprt, 1);
324 pg.addProgress(pgGet, 1);
325
326 final Progress pgF = pgImprt;
327 final String[] luid = new String[1];
328
329 try {
3040c4f0 330 new ConnectActionClientObject(host, port, key) {
edf79e5e
NR
331 @Override
332 public void action(Version serverVersion) throws Exception {
333 Progress pg = pgF;
334
ea734ab4
N
335 Object rep = sendCmd(this,
336 new Object[] { "IMPORT", url.toString() });
edf79e5e
NR
337
338 while (true) {
339 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
340 break;
341 }
342
343 rep = send(null);
344 }
345
346 pg.done();
347 luid[0] = (String) rep;
348 }
349
350 @Override
351 protected void onError(Exception e) {
352 Instance.getTraceHandler().error(e);
353 }
354 }.connect();
355 } catch (IOException e) {
356 Instance.getTraceHandler().error(e);
357 }
358
359 if (luid[0] == null) {
360 throw new IOException("Remote failure");
361 }
362
363 Story story = getStory(luid[0], pgGet);
364 pgGet.done();
365
366 pg.done();
367 return story;
368 }
369
370 @Override
371 // Could work (more slowly) without it
c8d48938
NR
372 protected synchronized void changeSTA(final String luid,
373 final String newSource, final String newTitle,
374 final String newAuthor, Progress pg) throws IOException {
edf79e5e
NR
375 final Progress pgF = pg == null ? new Progress() : pg;
376
377 try {
3040c4f0 378 new ConnectActionClientObject(host, port, key) {
edf79e5e
NR
379 @Override
380 public void action(Version serverVersion) throws Exception {
381 Progress pg = pgF;
382
ea734ab4
N
383 Object rep = sendCmd(this, new Object[] { "CHANGE_STA",
384 luid, newSource, newTitle, newAuthor });
edf79e5e
NR
385 while (true) {
386 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
387 break;
388 }
389
390 rep = send(null);
391 }
392 }
393
394 @Override
395 protected void onError(Exception e) {
396 Instance.getTraceHandler().error(e);
397 }
398 }.connect();
399 } catch (IOException e) {
400 Instance.getTraceHandler().error(e);
b9ce9cad 401 }
68e2c6d2
NR
402 }
403
ff05b828 404 @Override
2249988a 405 public synchronized File getFile(final String luid, Progress pg) {
ff05b828
NR
406 throw new java.lang.InternalError(
407 "Operation not supportorted on remote Libraries");
408 }
409
468b960b
NR
410 /**
411 * Stop the server.
412 */
413 public void exit() {
414 try {
3040c4f0 415 new ConnectActionClientObject(host, port, key) {
468b960b
NR
416 @Override
417 public void action(Version serverVersion) throws Exception {
ea734ab4 418 sendCmd(this, new Object[] { "EXIT" });
468b960b
NR
419 }
420
421 @Override
422 protected void onError(Exception e) {
423 Instance.getTraceHandler().error(e);
424 }
425 }.connect();
426 } catch (IOException e) {
427 Instance.getTraceHandler().error(e);
428 }
429 }
430
e272f05f
NR
431 @Override
432 public synchronized MetaData getInfo(String luid) {
433 List<MetaData> metas = getMetasList(luid, null);
434 if (!metas.isEmpty()) {
435 return metas.get(0);
436 }
437
438 return null;
439 }
440
14b57448 441 @Override
b9ce9cad 442 protected List<MetaData> getMetas(Progress pg) {
e272f05f
NR
443 return getMetasList("*", pg);
444 }
445
446 @Override
efa3c511
NR
447 protected void updateInfo(MetaData meta) {
448 // Will be taken care of directly server side
449 }
450
451 @Override
c8d48938 452 protected void invalidateInfo(String luid) {
efa3c511 453 // Will be taken care of directly server side
e272f05f
NR
454 }
455
456 // The following methods are only used by Save and Delete in BasicLibrary:
457
458 @Override
459 protected int getNextId() {
460 throw new java.lang.InternalError("Should not have been called");
461 }
462
463 @Override
464 protected void doDelete(String luid) throws IOException {
465 throw new java.lang.InternalError("Should not have been called");
466 }
467
468 @Override
469 protected Story doSave(Story story, Progress pg) throws IOException {
470 throw new java.lang.InternalError("Should not have been called");
471 }
472
473 //
474
475 /**
476 * Return the meta of the given story or a list of all known metas if the
477 * luid is "*".
9f51d8ab
NR
478 * <p>
479 * Will not get the covers.
e272f05f
NR
480 *
481 * @param luid
482 * the luid of the story or *
483 * @param pg
484 * the optional progress
485 *
486 *
487 * @return the metas
488 */
489 private List<MetaData> getMetasList(final String luid, Progress pg) {
b9ce9cad
NR
490 final Progress pgF = pg;
491 final List<MetaData> metas = new ArrayList<MetaData>();
74a40dfb 492
ff05b828 493 try {
3040c4f0 494 new ConnectActionClientObject(host, port, key) {
ff05b828
NR
495 @Override
496 public void action(Version serverVersion) throws Exception {
b9ce9cad
NR
497 Progress pg = pgF;
498 if (pg == null) {
499 pg = new Progress();
851dd538 500 }
74a40dfb 501
27eba894
NR
502 Object rep = sendCmd(this, new Object[] { "GET_METADATA",
503 luid });
74a40dfb 504
b9ce9cad
NR
505 while (true) {
506 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
507 break;
508 }
74a40dfb 509
b9ce9cad 510 rep = send(null);
ff05b828 511 }
851dd538 512
e272f05f
NR
513 if (rep instanceof MetaData[]) {
514 for (MetaData meta : (MetaData[]) rep) {
515 metas.add(meta);
516 }
517 } else if (rep != null) {
518 metas.add((MetaData) rep);
b9ce9cad 519 }
851dd538
NR
520 }
521
522 @Override
523 protected void onError(Exception e) {
524 Instance.getTraceHandler().error(e);
ff05b828
NR
525 }
526 }.connect();
ff05b828 527 } catch (Exception e) {
62c63b07 528 Instance.getTraceHandler().error(e);
ff05b828 529 }
b9ce9cad
NR
530
531 return metas;
532 }
ea734ab4
N
533
534 // IllegalArgumentException if key is bad
535 private Object sendCmd(ConnectActionClientObject action, Object[] params)
536 throws IOException, NoSuchFieldException, NoSuchMethodException,
537 ClassNotFoundException {
538 Object rep = action.send(params);
539
540 String hash = hashKey(key, "" + rep);
ea734ab4
N
541 return action.send(hash);
542 }
543
544 /**
545 * Return a hash that corresponds to the given key and the given random
546 * value.
547 *
548 * @param key
549 * the key (the secret)
550 *
551 * @param random
552 * the random value
553 *
554 * @return a hash that was computed using both
555 */
556 static String hashKey(String key, String random) {
557 return StringUtils.getMd5Hash(key + " <==> " + random);
558 }
b0e88ebd 559}