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