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