update nikiroo-utils, remote lib errors need some work
[fanfix.git] / src / be / nikiroo / fanfix / library / RemoteLibrary.java
1 package be.nikiroo.fanfix.library;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.URL;
6 import java.net.UnknownHostException;
7 import java.util.ArrayList;
8 import java.util.List;
9
10 import javax.net.ssl.SSLException;
11
12 import be.nikiroo.fanfix.Instance;
13 import be.nikiroo.fanfix.data.MetaData;
14 import be.nikiroo.fanfix.data.Story;
15 import be.nikiroo.utils.Image;
16 import be.nikiroo.utils.Progress;
17 import be.nikiroo.utils.serial.server.ConnectActionClientObject;
18
19 /**
20 * This {@link BasicLibrary} will access a remote server to list the available
21 * stories, and download the ones you try to load to the local directory
22 * specified in the configuration.
23 *
24 * @author niki
25 */
26 public class RemoteLibrary extends BasicLibrary {
27 private String host;
28 private int port;
29 private final String key;
30
31 // TODO: error handling is not up to par!
32
33 /**
34 * Create a {@link RemoteLibrary} linked to the given server.
35 *
36 * @param key
37 * the key that will allow us to exchange information with the
38 * server
39 * @param host
40 * the host to contact or NULL for localhost
41 * @param port
42 * the port to contact it on
43 */
44 public RemoteLibrary(String key, String host, int port) {
45 this.key = key;
46 this.host = host;
47 this.port = port;
48 }
49
50 @Override
51 public String getLibraryName() {
52 return host + ":" + port;
53 }
54
55 @Override
56 public Status getStatus() {
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() {
74 final Status[] result = new Status[1];
75
76 result[0] = Status.INVALID;
77
78 try {
79 new ConnectActionClientObject(host, port, key) {
80 @Override
81 public void action() throws Exception {
82 Object rep = send(new Object[] { "PING" });
83
84 if ("PONG".equals(rep)) {
85 result[0] = Status.READY;
86 } else {
87 result[0] = Status.UNAUTHORIZED;
88 }
89 }
90
91 @Override
92 protected void onError(Exception e) {
93 // if (e instanceof SSLException) {
94 result[0] = Status.UNAUTHORIZED;
95 // } else {
96 // result[0] = Status.UNAVAILABLE;
97 // }
98 }
99 }.connect();
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
108 return result[0];
109 }
110
111 @Override
112 public Image getCover(final String luid) {
113 if (!check()) {
114 return null;
115 }
116
117 final Image[] result = new Image[1];
118
119 try {
120 new ConnectActionClientObject(host, port, key) {
121 @Override
122 public void action() throws Exception {
123 Object rep = send(new Object[] { "GET_COVER", luid });
124 result[0] = (Image) rep;
125 }
126
127 @Override
128 protected void onError(Exception e) {
129 if (e instanceof SSLException) {
130 Instance.getTraceHandler().error(
131 "Connection refused (bad key)");
132 } else {
133 Instance.getTraceHandler().error(e);
134 }
135 }
136 }.connect();
137 } catch (Exception e) {
138 Instance.getTraceHandler().error(e);
139 }
140
141 return result[0];
142 }
143
144 @Override
145 public Image getCustomSourceCover(final String source) {
146 if (!check()) {
147 return null;
148 }
149
150 return getCustomCover(source, "SOURCE");
151 }
152
153 @Override
154 public Image getCustomAuthorCover(final String author) {
155 if (!check()) {
156 return null;
157 }
158
159 return getCustomCover(author, "AUTHOR");
160 }
161
162 // type: "SOURCE" or "AUTHOR"
163 private Image getCustomCover(final String source, final String type) {
164 if (!check()) {
165 return null;
166 }
167
168 final Image[] result = new Image[1];
169
170 try {
171 new ConnectActionClientObject(host, port, key) {
172 @Override
173 public void action() throws Exception {
174 Object rep = send(new Object[] { "GET_CUSTOM_COVER", type,
175 source });
176 result[0] = (Image) rep;
177 }
178
179 @Override
180 protected void onError(Exception e) {
181 if (e instanceof SSLException) {
182 Instance.getTraceHandler().error(
183 "Connection refused (bad key)");
184 } else {
185 Instance.getTraceHandler().error(e);
186 }
187 }
188 }.connect();
189 } catch (Exception e) {
190 Instance.getTraceHandler().error(e);
191 }
192
193 return result[0];
194 }
195
196 @Override
197 public synchronized Story getStory(final String luid, Progress pg) {
198 if (!check()) {
199 return null;
200 }
201
202 final Progress pgF = pg;
203 final Story[] result = new Story[1];
204
205 try {
206 new ConnectActionClientObject(host, port, key) {
207 @Override
208 public void action() throws Exception {
209 Progress pg = pgF;
210 if (pg == null) {
211 pg = new Progress();
212 }
213
214 Object rep = send(new Object[] { "GET_STORY", luid });
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>();
225 for (Object obj = send(null); obj != null; obj = send(null)) {
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) {
236 if (e instanceof SSLException) {
237 Instance.getTraceHandler().error(
238 "Connection refused (bad key)");
239 } else {
240 Instance.getTraceHandler().error(e);
241 }
242 }
243 }.connect();
244 } catch (Exception e) {
245 Instance.getTraceHandler().error(e);
246 }
247
248 return result[0];
249 }
250
251 @Override
252 public synchronized Story save(final Story story, final String luid,
253 Progress pg) throws IOException {
254 if (!check()) {
255 return null;
256 }
257
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;
270
271 new ConnectActionClientObject(host, port, key) {
272 @Override
273 public void action() throws Exception {
274 Progress pg = pgF;
275 if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
276 pg.setMinMax(0, (int) story.getMeta().getWords());
277 }
278
279 send(new Object[] { "SAVE_STORY", luid });
280
281 List<Object> list = RemoteLibraryServer.breakStory(story);
282 for (Object obj : list) {
283 send(obj);
284 pg.add(1);
285 }
286
287 luidSaved[0] = (String) send(null);
288
289 pg.done();
290 }
291
292 @Override
293 protected void onError(Exception e) {
294 if (e instanceof SSLException) {
295 Instance.getTraceHandler().error(
296 "Connection refused (bad key)");
297 } else {
298 Instance.getTraceHandler().error(e);
299 }
300 }
301 }.connect();
302
303 // because the meta changed:
304 MetaData meta = getInfo(luidSaved[0]);
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 }
312 story.setMeta(meta);
313
314 pg.done();
315
316 return story;
317 }
318
319 @Override
320 public synchronized void delete(final String luid) throws IOException {
321 if (!check()) {
322 throw new IOException("Library not ready");
323 }
324
325 new ConnectActionClientObject(host, port, key) {
326 @Override
327 public void action() throws Exception {
328 send(new Object[] { "DELETE_STORY", luid });
329 }
330
331 @Override
332 protected void onError(Exception e) {
333 if (e instanceof SSLException) {
334 Instance.getTraceHandler().error(
335 "Connection refused (bad key)");
336 } else {
337 Instance.getTraceHandler().error(e);
338 }
339 }
340 }.connect();
341 }
342
343 @Override
344 public void setSourceCover(final String source, final String luid) {
345 if (!check()) {
346 return;
347 }
348
349 setCover(source, luid, "SOURCE");
350 }
351
352 @Override
353 public void setAuthorCover(final String author, final String luid) {
354 if (!check()) {
355 return;
356 }
357
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) {
364 if (!check()) {
365 return;
366 }
367
368 try {
369 new ConnectActionClientObject(host, port, key) {
370 @Override
371 public void action() throws Exception {
372 send(new Object[] { "SET_COVER", type, value, luid });
373 }
374
375 @Override
376 protected void onError(Exception e) {
377 if (e instanceof SSLException) {
378 Instance.getTraceHandler().error(
379 "Connection refused (bad key)");
380 } else {
381 Instance.getTraceHandler().error(e);
382 }
383 }
384 }.connect();
385 } catch (IOException e) {
386 Instance.getTraceHandler().error(e);
387 }
388 }
389
390 @Override
391 // Could work (more slowly) without it
392 public Story imprt(final URL url, Progress pg) throws IOException {
393 if (!check()) {
394 return null;
395 }
396
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
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 {
418 new ConnectActionClientObject(host, port, key) {
419 @Override
420 public void action() throws Exception {
421 Progress pg = pgF;
422
423 Object rep = send(new Object[] { "IMPORT", url.toString() });
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) {
439 if (e instanceof SSLException) {
440 Instance.getTraceHandler().error(
441 "Connection refused (bad key)");
442 } else {
443 Instance.getTraceHandler().error(e);
444 }
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
464 protected synchronized void changeSTA(final String luid,
465 final String newSource, final String newTitle,
466 final String newAuthor, Progress pg) throws IOException {
467 if (!check()) {
468 return;
469 }
470
471 final Progress pgF = pg == null ? new Progress() : pg;
472
473 try {
474 new ConnectActionClientObject(host, port, key) {
475 @Override
476 public void action() throws Exception {
477 Progress pg = pgF;
478
479 Object rep = send(new Object[] { "CHANGE_STA", luid,
480 newSource, newTitle, newAuthor });
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) {
492 if (e instanceof SSLException) {
493 Instance.getTraceHandler().error(
494 "Connection refused (bad key)");
495 } else {
496 Instance.getTraceHandler().error(e);
497 }
498 }
499 }.connect();
500 } catch (IOException e) {
501 Instance.getTraceHandler().error(e);
502 }
503 }
504
505 @Override
506 public synchronized File getFile(final String luid, Progress pg) {
507 throw new java.lang.InternalError(
508 "Operation not supportorted on remote Libraries");
509 }
510
511 /**
512 * Stop the server.
513 */
514 public void exit() {
515 if (!check()) {
516 return;
517 }
518
519 try {
520 new ConnectActionClientObject(host, port, key) {
521 @Override
522 public void action() throws Exception {
523 send(new Object[] { "EXIT" });
524 }
525
526 @Override
527 protected void onError(Exception e) {
528 if (e instanceof SSLException) {
529 Instance.getTraceHandler().error(
530 "Connection refused (bad key)");
531 } else {
532 Instance.getTraceHandler().error(e);
533 }
534 }
535 }.connect();
536 } catch (IOException e) {
537 Instance.getTraceHandler().error(e);
538 }
539 }
540
541 @Override
542 public synchronized MetaData getInfo(String luid) {
543 if (!check()) {
544 return null;
545 }
546
547 List<MetaData> metas = getMetasList(luid, null);
548 if (!metas.isEmpty()) {
549 return metas.get(0);
550 }
551
552 return null;
553 }
554
555 @Override
556 protected List<MetaData> getMetas(Progress pg) {
557 return getMetasList("*", pg);
558 }
559
560 @Override
561 protected void updateInfo(MetaData meta) {
562 // Will be taken care of directly server side
563 }
564
565 @Override
566 protected void invalidateInfo(String luid) {
567 // Will be taken care of directly server side
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 "*".
592 * <p>
593 * Will not get the covers.
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) {
604 if (!check()) {
605 return null;
606 }
607
608 final Progress pgF = pg;
609 final List<MetaData> metas = new ArrayList<MetaData>();
610
611 try {
612 new ConnectActionClientObject(host, port, key) {
613 @Override
614 public void action() throws Exception {
615 Progress pg = pgF;
616 if (pg == null) {
617 pg = new Progress();
618 }
619
620 Object rep = send(new Object[] { "GET_METADATA", luid });
621
622 while (true) {
623 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
624 break;
625 }
626
627 rep = send(null);
628 }
629
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);
636 }
637 }
638
639 @Override
640 protected void onError(Exception e) {
641 if (e instanceof SSLException) {
642 Instance.getTraceHandler().error(
643 "Connection refused (bad key)");
644 } else {
645 Instance.getTraceHandler().error(e);
646 }
647 }
648 }.connect();
649 } catch (Exception e) {
650 Instance.getTraceHandler().error(e);
651 }
652
653 return metas;
654 }
655 }