remote string -> stream + encrypt
[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
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 62 @Override
a0565636 63 public void action() throws Exception {
7e51d91c 64 Object rep = send(new Object[] { "PING" });
ea734ab4 65
7e51d91c
NR
66 if ("PONG".equals(rep)) {
67 result[0] = Status.READY;
68 } else {
e6249b0f
NR
69 result[0] = Status.UNAUTORIZED;
70 }
71 }
72
73 @Override
74 protected void onError(Exception e) {
7e51d91c
NR
75 if (e instanceof SSLException) {
76 result[0] = Status.UNAUTORIZED;
77 } else {
78 result[0] = Status.UNAVAILABLE;
79 }
e6249b0f 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 100 @Override
a0565636 101 public void action() throws Exception {
3358927d 102 Object rep = send(new Object[] { "GET_COVER", luid });
16a81ef7 103 result[0] = (Image) rep;
b9ce9cad 104 }
b0e88ebd 105
b9ce9cad
NR
106 @Override
107 protected void onError(Exception e) {
7e51d91c
NR
108 if (e instanceof SSLException) {
109 Instance.getTraceHandler().error(
110 "Connection refused (bad key)");
111 } else {
112 Instance.getTraceHandler().error(e);
113 }
b9ce9cad
NR
114 }
115 }.connect();
116 } catch (Exception e) {
117 Instance.getTraceHandler().error(e);
118 }
b0e88ebd 119
b9ce9cad 120 return result[0];
085a2f9a
NR
121 }
122
123 @Override
e1de8087 124 public Image getCustomSourceCover(final String source) {
3989dfc5
NR
125 return getCustomCover(source, "SOURCE");
126 }
127
128 @Override
129 public Image getCustomAuthorCover(final String author) {
130 return getCustomCover(author, "AUTHOR");
131 }
132
133 // type: "SOURCE" or "AUTHOR"
134 private Image getCustomCover(final String source, final String type) {
16a81ef7 135 final Image[] result = new Image[1];
b9ce9cad
NR
136
137 try {
3040c4f0 138 new ConnectActionClientObject(host, port, key) {
b9ce9cad 139 @Override
a0565636 140 public void action() throws Exception {
3358927d
NR
141 Object rep = send(new Object[] { "GET_CUSTOM_COVER", type,
142 source });
16a81ef7 143 result[0] = (Image) rep;
b9ce9cad
NR
144 }
145
146 @Override
147 protected void onError(Exception e) {
7e51d91c
NR
148 if (e instanceof SSLException) {
149 Instance.getTraceHandler().error(
150 "Connection refused (bad key)");
151 } else {
152 Instance.getTraceHandler().error(e);
153 }
b9ce9cad
NR
154 }
155 }.connect();
156 } catch (Exception e) {
157 Instance.getTraceHandler().error(e);
158 }
159
160 return result[0];
b0e88ebd 161 }
68e2c6d2
NR
162
163 @Override
ff05b828 164 public synchronized Story getStory(final String luid, Progress pg) {
b9ce9cad
NR
165 final Progress pgF = pg;
166 final Story[] result = new Story[1];
68e2c6d2 167
b9ce9cad 168 try {
3040c4f0 169 new ConnectActionClientObject(host, port, key) {
b9ce9cad 170 @Override
a0565636 171 public void action() throws Exception {
b9ce9cad
NR
172 Progress pg = pgF;
173 if (pg == null) {
174 pg = new Progress();
175 }
176
3358927d 177 Object rep = send(new Object[] { "GET_STORY", luid });
b9ce9cad
NR
178
179 MetaData meta = null;
180 if (rep instanceof MetaData) {
181 meta = (MetaData) rep;
182 if (meta.getWords() <= Integer.MAX_VALUE) {
183 pg.setMinMax(0, (int) meta.getWords());
184 }
185 }
186
187 List<Object> list = new ArrayList<Object>();
27eba894 188 for (Object obj = send(null); obj != null; obj = send(null)) {
b9ce9cad
NR
189 list.add(obj);
190 pg.add(1);
191 }
192
193 result[0] = RemoteLibraryServer.rebuildStory(list);
194 pg.done();
195 }
196
197 @Override
198 protected void onError(Exception e) {
7e51d91c
NR
199 if (e instanceof SSLException) {
200 Instance.getTraceHandler().error(
201 "Connection refused (bad key)");
202 } else {
203 Instance.getTraceHandler().error(e);
204 }
b9ce9cad
NR
205 }
206 }.connect();
207 } catch (Exception e) {
208 Instance.getTraceHandler().error(e);
209 }
210
211 return result[0];
68e2c6d2
NR
212 }
213
214 @Override
b9ce9cad
NR
215 public synchronized Story save(final Story story, final String luid,
216 Progress pg) throws IOException {
0fa0fe95
NR
217 final String[] luidSaved = new String[1];
218 Progress pgSave = new Progress();
219 Progress pgRefresh = new Progress();
220 if (pg == null) {
221 pg = new Progress();
222 }
223
224 pg.setMinMax(0, 10);
225 pg.addProgress(pgSave, 9);
226 pg.addProgress(pgRefresh, 1);
227
228 final Progress pgF = pgSave;
b9ce9cad 229
3040c4f0 230 new ConnectActionClientObject(host, port, key) {
b9ce9cad 231 @Override
a0565636 232 public void action() throws Exception {
b9ce9cad 233 Progress pg = pgF;
b9ce9cad
NR
234 if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
235 pg.setMinMax(0, (int) story.getMeta().getWords());
236 }
237
3358927d 238 send(new Object[] { "SAVE_STORY", luid });
b9ce9cad
NR
239
240 List<Object> list = RemoteLibraryServer.breakStory(story);
241 for (Object obj : list) {
242 send(obj);
243 pg.add(1);
244 }
245
edf79e5e 246 luidSaved[0] = (String) send(null);
0fa0fe95 247
b9ce9cad
NR
248 pg.done();
249 }
250
251 @Override
252 protected void onError(Exception e) {
7e51d91c
NR
253 if (e instanceof SSLException) {
254 Instance.getTraceHandler().error(
255 "Connection refused (bad key)");
256 } else {
257 Instance.getTraceHandler().error(e);
258 }
b9ce9cad
NR
259 }
260 }.connect();
085a2f9a
NR
261
262 // because the meta changed:
edf79e5e 263 MetaData meta = getInfo(luidSaved[0]);
efa3c511
NR
264 if (story.getMeta().getClass() != null) {
265 // If already available locally:
266 meta.setCover(story.getMeta().getCover());
267 } else {
268 // If required:
269 meta.setCover(getCover(meta.getLuid()));
270 }
edf79e5e 271 story.setMeta(meta);
0fa0fe95
NR
272
273 pg.done();
085a2f9a
NR
274
275 return story;
68e2c6d2
NR
276 }
277
278 @Override
b9ce9cad 279 public synchronized void delete(final String luid) throws IOException {
3040c4f0 280 new ConnectActionClientObject(host, port, key) {
b9ce9cad 281 @Override
a0565636 282 public void action() throws Exception {
3358927d 283 send(new Object[] { "DELETE_STORY", luid });
b9ce9cad
NR
284 }
285
286 @Override
287 protected void onError(Exception e) {
7e51d91c
NR
288 if (e instanceof SSLException) {
289 Instance.getTraceHandler().error(
290 "Connection refused (bad key)");
291 } else {
292 Instance.getTraceHandler().error(e);
293 }
b9ce9cad
NR
294 }
295 }.connect();
68e2c6d2
NR
296 }
297
298 @Override
b9ce9cad 299 public void setSourceCover(final String source, final String luid) {
3989dfc5
NR
300 setCover(source, luid, "SOURCE");
301 }
302
303 @Override
304 public void setAuthorCover(final String author, final String luid) {
305 setCover(author, luid, "AUTHOR");
306 }
307
308 // type = "SOURCE" | "AUTHOR"
309 private void setCover(final String value, final String luid,
310 final String type) {
b9ce9cad 311 try {
3040c4f0 312 new ConnectActionClientObject(host, port, key) {
b9ce9cad 313 @Override
a0565636 314 public void action() throws Exception {
3358927d 315 send(new Object[] { "SET_COVER", type, value, luid });
b9ce9cad
NR
316 }
317
318 @Override
319 protected void onError(Exception e) {
7e51d91c
NR
320 if (e instanceof SSLException) {
321 Instance.getTraceHandler().error(
322 "Connection refused (bad key)");
323 } else {
324 Instance.getTraceHandler().error(e);
325 }
b9ce9cad
NR
326 }
327 }.connect();
328 } catch (IOException e) {
329 Instance.getTraceHandler().error(e);
edf79e5e
NR
330 }
331 }
332
333 @Override
334 // Could work (more slowly) without it
335 public Story imprt(final URL url, Progress pg) throws IOException {
00f6344a
NR
336 // Import the file locally if it is actually a file
337 if (url == null || url.getProtocol().equalsIgnoreCase("file")) {
338 return super.imprt(url, pg);
339 }
340
341 // Import it remotely if it is an URL
342
edf79e5e
NR
343 if (pg == null) {
344 pg = new Progress();
345 }
346
347 pg.setMinMax(0, 2);
348 Progress pgImprt = new Progress();
349 Progress pgGet = new Progress();
350 pg.addProgress(pgImprt, 1);
351 pg.addProgress(pgGet, 1);
352
353 final Progress pgF = pgImprt;
354 final String[] luid = new String[1];
355
356 try {
3040c4f0 357 new ConnectActionClientObject(host, port, key) {
edf79e5e 358 @Override
a0565636 359 public void action() throws Exception {
edf79e5e
NR
360 Progress pg = pgF;
361
3358927d 362 Object rep = send(new Object[] { "IMPORT", url.toString() });
edf79e5e
NR
363
364 while (true) {
365 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
366 break;
367 }
368
369 rep = send(null);
370 }
371
372 pg.done();
373 luid[0] = (String) rep;
374 }
375
376 @Override
377 protected void onError(Exception e) {
7e51d91c
NR
378 if (e instanceof SSLException) {
379 Instance.getTraceHandler().error(
380 "Connection refused (bad key)");
381 } else {
382 Instance.getTraceHandler().error(e);
383 }
edf79e5e
NR
384 }
385 }.connect();
386 } catch (IOException e) {
387 Instance.getTraceHandler().error(e);
388 }
389
390 if (luid[0] == null) {
391 throw new IOException("Remote failure");
392 }
393
394 Story story = getStory(luid[0], pgGet);
395 pgGet.done();
396
397 pg.done();
398 return story;
399 }
400
401 @Override
402 // Could work (more slowly) without it
c8d48938
NR
403 protected synchronized void changeSTA(final String luid,
404 final String newSource, final String newTitle,
405 final String newAuthor, Progress pg) throws IOException {
edf79e5e
NR
406 final Progress pgF = pg == null ? new Progress() : pg;
407
408 try {
3040c4f0 409 new ConnectActionClientObject(host, port, key) {
edf79e5e 410 @Override
a0565636 411 public void action() throws Exception {
edf79e5e
NR
412 Progress pg = pgF;
413
3358927d
NR
414 Object rep = send(new Object[] { "CHANGE_STA", luid,
415 newSource, newTitle, newAuthor });
edf79e5e
NR
416 while (true) {
417 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
418 break;
419 }
420
421 rep = send(null);
422 }
423 }
424
425 @Override
426 protected void onError(Exception e) {
7e51d91c
NR
427 if (e instanceof SSLException) {
428 Instance.getTraceHandler().error(
429 "Connection refused (bad key)");
430 } else {
431 Instance.getTraceHandler().error(e);
432 }
edf79e5e
NR
433 }
434 }.connect();
435 } catch (IOException e) {
436 Instance.getTraceHandler().error(e);
b9ce9cad 437 }
68e2c6d2
NR
438 }
439
ff05b828 440 @Override
2249988a 441 public synchronized File getFile(final String luid, Progress pg) {
ff05b828
NR
442 throw new java.lang.InternalError(
443 "Operation not supportorted on remote Libraries");
444 }
445
468b960b
NR
446 /**
447 * Stop the server.
448 */
449 public void exit() {
450 try {
3040c4f0 451 new ConnectActionClientObject(host, port, key) {
468b960b 452 @Override
a0565636 453 public void action() throws Exception {
3358927d 454 send(new Object[] { "EXIT" });
468b960b
NR
455 }
456
457 @Override
458 protected void onError(Exception e) {
7e51d91c
NR
459 if (e instanceof SSLException) {
460 Instance.getTraceHandler().error(
461 "Connection refused (bad key)");
462 } else {
463 Instance.getTraceHandler().error(e);
464 }
468b960b
NR
465 }
466 }.connect();
467 } catch (IOException e) {
468 Instance.getTraceHandler().error(e);
469 }
470 }
471
e272f05f
NR
472 @Override
473 public synchronized MetaData getInfo(String luid) {
474 List<MetaData> metas = getMetasList(luid, null);
475 if (!metas.isEmpty()) {
476 return metas.get(0);
477 }
478
479 return null;
480 }
481
14b57448 482 @Override
b9ce9cad 483 protected List<MetaData> getMetas(Progress pg) {
e272f05f
NR
484 return getMetasList("*", pg);
485 }
486
487 @Override
efa3c511
NR
488 protected void updateInfo(MetaData meta) {
489 // Will be taken care of directly server side
490 }
491
492 @Override
c8d48938 493 protected void invalidateInfo(String luid) {
efa3c511 494 // Will be taken care of directly server side
e272f05f
NR
495 }
496
497 // The following methods are only used by Save and Delete in BasicLibrary:
498
499 @Override
500 protected int getNextId() {
501 throw new java.lang.InternalError("Should not have been called");
502 }
503
504 @Override
505 protected void doDelete(String luid) throws IOException {
506 throw new java.lang.InternalError("Should not have been called");
507 }
508
509 @Override
510 protected Story doSave(Story story, Progress pg) throws IOException {
511 throw new java.lang.InternalError("Should not have been called");
512 }
513
514 //
515
516 /**
517 * Return the meta of the given story or a list of all known metas if the
518 * luid is "*".
9f51d8ab
NR
519 * <p>
520 * Will not get the covers.
e272f05f
NR
521 *
522 * @param luid
523 * the luid of the story or *
524 * @param pg
525 * the optional progress
526 *
527 *
528 * @return the metas
529 */
530 private List<MetaData> getMetasList(final String luid, Progress pg) {
b9ce9cad
NR
531 final Progress pgF = pg;
532 final List<MetaData> metas = new ArrayList<MetaData>();
74a40dfb 533
ff05b828 534 try {
3040c4f0 535 new ConnectActionClientObject(host, port, key) {
ff05b828 536 @Override
a0565636 537 public void action() throws Exception {
b9ce9cad
NR
538 Progress pg = pgF;
539 if (pg == null) {
540 pg = new Progress();
851dd538 541 }
74a40dfb 542
3358927d 543 Object rep = send(new Object[] { "GET_METADATA", luid });
74a40dfb 544
b9ce9cad
NR
545 while (true) {
546 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
547 break;
548 }
74a40dfb 549
b9ce9cad 550 rep = send(null);
ff05b828 551 }
851dd538 552
e272f05f
NR
553 if (rep instanceof MetaData[]) {
554 for (MetaData meta : (MetaData[]) rep) {
555 metas.add(meta);
556 }
557 } else if (rep != null) {
558 metas.add((MetaData) rep);
b9ce9cad 559 }
851dd538
NR
560 }
561
562 @Override
563 protected void onError(Exception e) {
7e51d91c
NR
564 if (e instanceof SSLException) {
565 Instance.getTraceHandler().error(
566 "Connection refused (bad key)");
567 } else {
568 Instance.getTraceHandler().error(e);
569 }
ff05b828
NR
570 }
571 }.connect();
ff05b828 572 } catch (Exception e) {
62c63b07 573 Instance.getTraceHandler().error(e);
ff05b828 574 }
b9ce9cad
NR
575
576 return metas;
577 }
b0e88ebd 578}