Merge branch 'master' into search
[nikiroo-utils.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 be.nikiroo.fanfix.Instance;
11 import be.nikiroo.fanfix.data.MetaData;
12 import be.nikiroo.fanfix.data.Story;
13 import be.nikiroo.utils.Image;
14 import be.nikiroo.utils.Progress;
15 import be.nikiroo.utils.StringUtils;
16 import be.nikiroo.utils.Version;
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 md5;
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.md5 = StringUtils.getMd5Hash(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 ConnectActionClientObject action = null;
60 try {
61 action = new ConnectActionClientObject(host, port, true) {
62 @Override
63 public void action(Version serverVersion) throws Exception {
64 Object rep = send(new Object[] { md5, "PING" });
65 if ("PONG".equals(rep)) {
66 result[0] = Status.READY;
67 } else {
68 result[0] = Status.UNAUTORIZED;
69 }
70 }
71
72 @Override
73 protected void onError(Exception e) {
74 result[0] = Status.UNAVAILABLE;
75 }
76 };
77
78 } catch (UnknownHostException e) {
79 result[0] = Status.INVALID;
80 } catch (IllegalArgumentException e) {
81 result[0] = Status.INVALID;
82 } catch (Exception e) {
83 result[0] = Status.UNAVAILABLE;
84 }
85
86 if (action != null) {
87 try {
88 action.connect();
89 } catch (Exception e) {
90 result[0] = Status.UNAVAILABLE;
91 }
92 }
93
94 return result[0];
95 }
96
97 @Override
98 public Image getCover(final String luid) {
99 final Image[] result = new Image[1];
100
101 try {
102 new ConnectActionClientObject(host, port, true) {
103 @Override
104 public void action(Version serverVersion) throws Exception {
105 Object rep = send(new Object[] { md5, "GET_COVER", luid });
106 result[0] = (Image) rep;
107 }
108
109 @Override
110 protected void onError(Exception e) {
111 Instance.getTraceHandler().error(e);
112 }
113 }.connect();
114 } catch (Exception e) {
115 Instance.getTraceHandler().error(e);
116 }
117
118 return result[0];
119 }
120
121 @Override
122 public Image getCustomSourceCover(final String source) {
123 return getCustomCover(source, "SOURCE");
124 }
125
126 @Override
127 public Image getCustomAuthorCover(final String author) {
128 return getCustomCover(author, "AUTHOR");
129 }
130
131 // type: "SOURCE" or "AUTHOR"
132 private Image getCustomCover(final String source, final String type) {
133 final Image[] result = new Image[1];
134
135 try {
136 new ConnectActionClientObject(host, port, true) {
137 @Override
138 public void action(Version serverVersion) throws Exception {
139 Object rep = send(new Object[] { md5, "GET_CUSTOM_COVER",
140 type, source });
141 result[0] = (Image) rep;
142 }
143
144 @Override
145 protected void onError(Exception e) {
146 Instance.getTraceHandler().error(e);
147 }
148 }.connect();
149 } catch (Exception e) {
150 Instance.getTraceHandler().error(e);
151 }
152
153 return result[0];
154 }
155
156 @Override
157 public synchronized Story getStory(final String luid, Progress pg) {
158 final Progress pgF = pg;
159 final Story[] result = new Story[1];
160
161 try {
162 new ConnectActionClientObject(host, port, true) {
163 @Override
164 public void action(Version serverVersion) throws Exception {
165 Progress pg = pgF;
166 if (pg == null) {
167 pg = new Progress();
168 }
169
170 Object rep = send(new Object[] { md5, "GET_STORY", luid });
171
172 MetaData meta = null;
173 if (rep instanceof MetaData) {
174 meta = (MetaData) rep;
175 if (meta.getWords() <= Integer.MAX_VALUE) {
176 pg.setMinMax(0, (int) meta.getWords());
177 }
178 }
179
180 List<Object> list = new ArrayList<Object>();
181 for (Object obj = send(null); obj != null; obj = send(null)) {
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];
200 }
201
202 @Override
203 public synchronized Story save(final Story story, final String luid,
204 Progress pg) throws IOException {
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;
217
218 new ConnectActionClientObject(host, port, true) {
219 @Override
220 public void action(Version serverVersion) throws Exception {
221 Progress pg = pgF;
222 if (story.getMeta().getWords() <= Integer.MAX_VALUE) {
223 pg.setMinMax(0, (int) story.getMeta().getWords());
224 }
225
226 send(new Object[] { md5, "SAVE_STORY", luid });
227
228 List<Object> list = RemoteLibraryServer.breakStory(story);
229 for (Object obj : list) {
230 send(obj);
231 pg.add(1);
232 }
233
234 luidSaved[0] = (String) send(null);
235
236 pg.done();
237 }
238
239 @Override
240 protected void onError(Exception e) {
241 Instance.getTraceHandler().error(e);
242 }
243 }.connect();
244
245 // because the meta changed:
246 MetaData meta = getInfo(luidSaved[0]);
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 }
254 story.setMeta(meta);
255
256 pg.done();
257
258 return story;
259 }
260
261 @Override
262 public synchronized void delete(final String luid) throws IOException {
263 new ConnectActionClientObject(host, port, true) {
264 @Override
265 public void action(Version serverVersion) throws Exception {
266 send(new Object[] { md5, "DELETE_STORY", luid });
267 }
268
269 @Override
270 protected void onError(Exception e) {
271 Instance.getTraceHandler().error(e);
272 }
273 }.connect();
274 }
275
276 @Override
277 public void setSourceCover(final String source, final String luid) {
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) {
289 try {
290 new ConnectActionClientObject(host, port, true) {
291 @Override
292 public void action(Version serverVersion) throws Exception {
293 send(new Object[] { md5, "SET_COVER", type, value, luid });
294 }
295
296 @Override
297 protected void onError(Exception e) {
298 Instance.getTraceHandler().error(e);
299 }
300 }.connect();
301 } catch (IOException e) {
302 Instance.getTraceHandler().error(e);
303 }
304 }
305
306 @Override
307 // Could work (more slowly) without it
308 public Story imprt(final URL url, Progress pg) throws IOException {
309 // Import the file locally if it is actually a file
310 if (url == null || url.getProtocol().equalsIgnoreCase("file")) {
311 return super.imprt(url, pg);
312 }
313
314 // Import it remotely if it is an URL
315
316 if (pg == null) {
317 pg = new Progress();
318 }
319
320 pg.setMinMax(0, 2);
321 Progress pgImprt = new Progress();
322 Progress pgGet = new Progress();
323 pg.addProgress(pgImprt, 1);
324 pg.addProgress(pgGet, 1);
325
326 final Progress pgF = pgImprt;
327 final String[] luid = new String[1];
328
329 try {
330 new ConnectActionClientObject(host, port, true) {
331 @Override
332 public void action(Version serverVersion) throws Exception {
333 Progress pg = pgF;
334
335 Object rep = send(new Object[] { md5, "IMPORT",
336 url.toString() });
337
338 while (true) {
339 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
340 break;
341 }
342
343 rep = send(null);
344 }
345
346 pg.done();
347 luid[0] = (String) rep;
348 }
349
350 @Override
351 protected void onError(Exception e) {
352 Instance.getTraceHandler().error(e);
353 }
354 }.connect();
355 } catch (IOException e) {
356 Instance.getTraceHandler().error(e);
357 }
358
359 if (luid[0] == null) {
360 throw new IOException("Remote failure");
361 }
362
363 Story story = getStory(luid[0], pgGet);
364 pgGet.done();
365
366 pg.done();
367 return story;
368 }
369
370 @Override
371 // Could work (more slowly) without it
372 protected synchronized void changeSTA(final String luid,
373 final String newSource, final String newTitle,
374 final String newAuthor, Progress pg) throws IOException {
375 final Progress pgF = pg == null ? new Progress() : pg;
376
377 try {
378 new ConnectActionClientObject(host, port, true) {
379 @Override
380 public void action(Version serverVersion) throws Exception {
381 Progress pg = pgF;
382
383 Object rep = send(new Object[] { md5, "CHANGE_STA", luid,
384 newSource, newTitle, newAuthor });
385 while (true) {
386 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
387 break;
388 }
389
390 rep = send(null);
391 }
392 }
393
394 @Override
395 protected void onError(Exception e) {
396 Instance.getTraceHandler().error(e);
397 }
398 }.connect();
399 } catch (IOException e) {
400 Instance.getTraceHandler().error(e);
401 }
402 }
403
404 @Override
405 public synchronized File getFile(final String luid, Progress pg) {
406 throw new java.lang.InternalError(
407 "Operation not supportorted on remote Libraries");
408 }
409
410 /**
411 * Stop the server.
412 */
413 public void exit() {
414 try {
415 new ConnectActionClientObject(host, port, true) {
416 @Override
417 public void action(Version serverVersion) throws Exception {
418 send(new Object[] { md5, "EXIT" });
419 }
420
421 @Override
422 protected void onError(Exception e) {
423 Instance.getTraceHandler().error(e);
424 }
425 }.connect();
426 } catch (IOException e) {
427 Instance.getTraceHandler().error(e);
428 }
429 }
430
431 @Override
432 public synchronized MetaData getInfo(String luid) {
433 List<MetaData> metas = getMetasList(luid, null);
434 if (!metas.isEmpty()) {
435 return metas.get(0);
436 }
437
438 return null;
439 }
440
441 @Override
442 protected List<MetaData> getMetas(Progress pg) {
443 return getMetasList("*", pg);
444 }
445
446 @Override
447 protected void updateInfo(MetaData meta) {
448 // Will be taken care of directly server side
449 }
450
451 @Override
452 protected void invalidateInfo(String luid) {
453 // Will be taken care of directly server side
454 }
455
456 // The following methods are only used by Save and Delete in BasicLibrary:
457
458 @Override
459 protected int getNextId() {
460 throw new java.lang.InternalError("Should not have been called");
461 }
462
463 @Override
464 protected void doDelete(String luid) throws IOException {
465 throw new java.lang.InternalError("Should not have been called");
466 }
467
468 @Override
469 protected Story doSave(Story story, Progress pg) throws IOException {
470 throw new java.lang.InternalError("Should not have been called");
471 }
472
473 //
474
475 /**
476 * Return the meta of the given story or a list of all known metas if the
477 * luid is "*".
478 * <p>
479 * Will not get the covers.
480 *
481 * @param luid
482 * the luid of the story or *
483 * @param pg
484 * the optional progress
485 *
486 *
487 * @return the metas
488 */
489 private List<MetaData> getMetasList(final String luid, Progress pg) {
490 final Progress pgF = pg;
491 final List<MetaData> metas = new ArrayList<MetaData>();
492
493 try {
494 new ConnectActionClientObject(host, port, true) {
495 @Override
496 public void action(Version serverVersion) throws Exception {
497 Progress pg = pgF;
498 if (pg == null) {
499 pg = new Progress();
500 }
501
502 Object rep = send(new Object[] { md5, "GET_METADATA", luid });
503
504 while (true) {
505 if (!RemoteLibraryServer.updateProgress(pg, rep)) {
506 break;
507 }
508
509 rep = send(null);
510 }
511
512 if (rep instanceof MetaData[]) {
513 for (MetaData meta : (MetaData[]) rep) {
514 metas.add(meta);
515 }
516 } else if (rep != null) {
517 metas.add((MetaData) rep);
518 }
519 }
520
521 @Override
522 protected void onError(Exception e) {
523 Instance.getTraceHandler().error(e);
524 }
525 }.connect();
526 } catch (Exception e) {
527 Instance.getTraceHandler().error(e);
528 }
529
530 return metas;
531 }
532 }