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