remote string -> stream + encrypt
[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 /**
32 * Create a {@link RemoteLibrary} linked to the given server.
33 *
34 * @param key
35 * the key that will allow us to exchange information with the
36 * server
37 * @param host
38 * the host to contact or NULL for localhost
39 * @param port
40 * the port to contact it on
41 */
42 public RemoteLibrary(String key, String host, int port) {
43 this.key = key;
44 this.host = host;
45 this.port = port;
46 }
47
48 @Override
49 public String getLibraryName() {
50 return host + ":" + port;
51 }
52
53 @Override
54 public Status getStatus() {
55 final Status[] result = new Status[1];
56
57 result[0] = Status.INVALID;
58
59 try {
60 Instance.getTraceHandler().trace("Getting remote lib status...");
61 new ConnectActionClientObject(host, port, key) {
62 @Override
63 public void action() throws Exception {
64 Object rep = send(new Object[] { "PING" });
65
66 if ("PONG".equals(rep)) {
67 result[0] = Status.READY;
68 } else {
69 result[0] = Status.UNAUTORIZED;
70 }
71 }
72
73 @Override
74 protected void onError(Exception e) {
75 if (e instanceof SSLException) {
76 result[0] = Status.UNAUTORIZED;
77 } else {
78 result[0] = Status.UNAVAILABLE;
79 }
80 }
81 }.connect();
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
90 Instance.getTraceHandler().trace("Remote lib status: " + result[0]);
91 return result[0];
92 }
93
94 @Override
95 public Image getCover(final String luid) {
96 final Image[] result = new Image[1];
97
98 try {
99 new ConnectActionClientObject(host, port, key) {
100 @Override
101 public void action() throws Exception {
102 Object rep = send(new Object[] { "GET_COVER", luid });
103 result[0] = (Image) rep;
104 }
105
106 @Override
107 protected void onError(Exception e) {
108 if (e instanceof SSLException) {
109 Instance.getTraceHandler().error(
110 "Connection refused (bad key)");
111 } else {
112 Instance.getTraceHandler().error(e);
113 }
114 }
115 }.connect();
116 } catch (Exception e) {
117 Instance.getTraceHandler().error(e);
118 }
119
120 return result[0];
121 }
122
123 @Override
124 public Image getCustomSourceCover(final String source) {
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) {
135 final Image[] result = new Image[1];
136
137 try {
138 new ConnectActionClientObject(host, port, key) {
139 @Override
140 public void action() throws Exception {
141 Object rep = send(new Object[] { "GET_CUSTOM_COVER", type,
142 source });
143 result[0] = (Image) rep;
144 }
145
146 @Override
147 protected void onError(Exception e) {
148 if (e instanceof SSLException) {
149 Instance.getTraceHandler().error(
150 "Connection refused (bad key)");
151 } else {
152 Instance.getTraceHandler().error(e);
153 }
154 }
155 }.connect();
156 } catch (Exception e) {
157 Instance.getTraceHandler().error(e);
158 }
159
160 return result[0];
161 }
162
163 @Override
164 public synchronized Story getStory(final String luid, Progress pg) {
165 final Progress pgF = pg;
166 final Story[] result = new Story[1];
167
168 try {
169 new ConnectActionClientObject(host, port, key) {
170 @Override
171 public void action() throws Exception {
172 Progress pg = pgF;
173 if (pg == null) {
174 pg = new Progress();
175 }
176
177 Object rep = send(new Object[] { "GET_STORY", luid });
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>();
188 for (Object obj = send(null); obj != null; obj = send(null)) {
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) {
199 if (e instanceof SSLException) {
200 Instance.getTraceHandler().error(
201 "Connection refused (bad key)");
202 } else {
203 Instance.getTraceHandler().error(e);
204 }
205 }
206 }.connect();
207 } catch (Exception e) {
208 Instance.getTraceHandler().error(e);
209 }
210
211 return result[0];
212 }
213
214 @Override
215 public synchronized Story save(final Story story, final String luid,
216 Progress pg) throws IOException {
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;
229
230 new ConnectActionClientObject(host, port, key) {
231 @Override
232 public void action() throws Exception {
233 Progress pg = pgF;
234 if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
235 pg.setMinMax(0, (int) story.getMeta().getWords());
236 }
237
238 send(new Object[] { "SAVE_STORY", luid });
239
240 List<Object> list = RemoteLibraryServer.breakStory(story);
241 for (Object obj : list) {
242 send(obj);
243 pg.add(1);
244 }
245
246 luidSaved[0] = (String) send(null);
247
248 pg.done();
249 }
250
251 @Override
252 protected void onError(Exception e) {
253 if (e instanceof SSLException) {
254 Instance.getTraceHandler().error(
255 "Connection refused (bad key)");
256 } else {
257 Instance.getTraceHandler().error(e);
258 }
259 }
260 }.connect();
261
262 // because the meta changed:
263 MetaData meta = getInfo(luidSaved[0]);
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 }
271 story.setMeta(meta);
272
273 pg.done();
274
275 return story;
276 }
277
278 @Override
279 public synchronized void delete(final String luid) throws IOException {
280 new ConnectActionClientObject(host, port, key) {
281 @Override
282 public void action() throws Exception {
283 send(new Object[] { "DELETE_STORY", luid });
284 }
285
286 @Override
287 protected void onError(Exception e) {
288 if (e instanceof SSLException) {
289 Instance.getTraceHandler().error(
290 "Connection refused (bad key)");
291 } else {
292 Instance.getTraceHandler().error(e);
293 }
294 }
295 }.connect();
296 }
297
298 @Override
299 public void setSourceCover(final String source, final String luid) {
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) {
311 try {
312 new ConnectActionClientObject(host, port, key) {
313 @Override
314 public void action() throws Exception {
315 send(new Object[] { "SET_COVER", type, value, luid });
316 }
317
318 @Override
319 protected void onError(Exception e) {
320 if (e instanceof SSLException) {
321 Instance.getTraceHandler().error(
322 "Connection refused (bad key)");
323 } else {
324 Instance.getTraceHandler().error(e);
325 }
326 }
327 }.connect();
328 } catch (IOException e) {
329 Instance.getTraceHandler().error(e);
330 }
331 }
332
333 @Override
334 // Could work (more slowly) without it
335 public Story imprt(final URL url, Progress pg) throws IOException {
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
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 {
357 new ConnectActionClientObject(host, port, key) {
358 @Override
359 public void action() throws Exception {
360 Progress pg = pgF;
361
362 Object rep = send(new Object[] { "IMPORT", url.toString() });
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) {
378 if (e instanceof SSLException) {
379 Instance.getTraceHandler().error(
380 "Connection refused (bad key)");
381 } else {
382 Instance.getTraceHandler().error(e);
383 }
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
403 protected synchronized void changeSTA(final String luid,
404 final String newSource, final String newTitle,
405 final String newAuthor, Progress pg) throws IOException {
406 final Progress pgF = pg == null ? new Progress() : pg;
407
408 try {
409 new ConnectActionClientObject(host, port, key) {
410 @Override
411 public void action() throws Exception {
412 Progress pg = pgF;
413
414 Object rep = send(new Object[] { "CHANGE_STA", luid,
415 newSource, newTitle, newAuthor });
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) {
427 if (e instanceof SSLException) {
428 Instance.getTraceHandler().error(
429 "Connection refused (bad key)");
430 } else {
431 Instance.getTraceHandler().error(e);
432 }
433 }
434 }.connect();
435 } catch (IOException e) {
436 Instance.getTraceHandler().error(e);
437 }
438 }
439
440 @Override
441 public synchronized File getFile(final String luid, Progress pg) {
442 throw new java.lang.InternalError(
443 "Operation not supportorted on remote Libraries");
444 }
445
446 /**
447 * Stop the server.
448 */
449 public void exit() {
450 try {
451 new ConnectActionClientObject(host, port, key) {
452 @Override
453 public void action() throws Exception {
454 send(new Object[] { "EXIT" });
455 }
456
457 @Override
458 protected void onError(Exception e) {
459 if (e instanceof SSLException) {
460 Instance.getTraceHandler().error(
461 "Connection refused (bad key)");
462 } else {
463 Instance.getTraceHandler().error(e);
464 }
465 }
466 }.connect();
467 } catch (IOException e) {
468 Instance.getTraceHandler().error(e);
469 }
470 }
471
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
482 @Override
483 protected List<MetaData> getMetas(Progress pg) {
484 return getMetasList("*", pg);
485 }
486
487 @Override
488 protected void updateInfo(MetaData meta) {
489 // Will be taken care of directly server side
490 }
491
492 @Override
493 protected void invalidateInfo(String luid) {
494 // Will be taken care of directly server side
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 "*".
519 * <p>
520 * Will not get the covers.
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) {
531 final Progress pgF = pg;
532 final List<MetaData> metas = new ArrayList<MetaData>();
533
534 try {
535 new ConnectActionClientObject(host, port, key) {
536 @Override
537 public void action() throws Exception {
538 Progress pg = pgF;
539 if (pg == null) {
540 pg = new Progress();
541 }
542
543 Object rep = send(new Object[] { "GET_METADATA", luid });
544
545 while (true) {
546 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
547 break;
548 }
549
550 rep = send(null);
551 }
552
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);
559 }
560 }
561
562 @Override
563 protected void onError(Exception e) {
564 if (e instanceof SSLException) {
565 Instance.getTraceHandler().error(
566 "Connection refused (bad key)");
567 } else {
568 Instance.getTraceHandler().error(e);
569 }
570 }
571 }.connect();
572 } catch (Exception e) {
573 Instance.getTraceHandler().error(e);
574 }
575
576 return metas;
577 }
578 }