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