1 package be
.nikiroo
.fanfix
.library
;
4 import java
.io
.IOException
;
6 import java
.net
.UnknownHostException
;
7 import java
.util
.ArrayList
;
10 import javax
.net
.ssl
.SSLException
;
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
.Version
;
18 import be
.nikiroo
.utils
.serial
.server
.ConnectActionClientObject
;
21 * This {@link BasicLibrary} will access a remote server to list the available
22 * stories, and download the ones you try to load to the local directory
23 * specified in the configuration.
25 * This remote library uses a custom fanfix:// protocol.
29 public class RemoteLibrary
extends BasicLibrary
{
30 interface RemoteAction
{
31 public void action(ConnectActionClientObject action
) throws Exception
;
34 class RemoteConnectAction
extends ConnectActionClientObject
{
35 public RemoteConnectAction() throws IOException
{
36 super(host
, port
, key
);
40 public Object
send(Object data
)
41 throws IOException
, NoSuchFieldException
, NoSuchMethodException
,
42 ClassNotFoundException
{
43 Object rep
= super.send(data
);
44 if (rep
instanceof RemoteLibraryException
) {
45 RemoteLibraryException remoteEx
= (RemoteLibraryException
) rep
;
46 throw remoteEx
.unwrapException();
55 private final String key
;
56 private final String subkey
;
58 // informative only (server will make the actual checks)
62 * Create a {@link RemoteLibrary} linked to the given server.
64 * Note that the key is structured:
65 * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>(|<b>wl</b>)(|<b>bl</b>)(|<b>rw</b>)</tt>
67 * Note that anything before the first pipe (<tt>|</tt>) character is
68 * considered to be the encryption key, anything after that character is
69 * called the subkey (including the other pipe characters and flags!).
71 * This is important because the subkey (including the pipe characters and
72 * flags) must be present as-is in the server configuration file to be
75 * <li><b><i>xxx</i></b>: the encryption key used to communicate with the
77 * <li><b><i>yyy</i></b>: the secondary key</li>
78 * <li><b>rw</b>: flag to allow read and write access if it is not the
79 * default on this server</li>
80 * <li><b>bl</b>: flag to bypass the blacklist (if it exists)</li>
81 * <li><b>wl</b>: flag to bypass the whitelist if it exists</li>
86 * <li><b>my_key</b>: normal connection, will take the default server
88 * <li><b>my_key|agzyzz|wl|bl</b>: will ask to bypass the black list and the
89 * white list (if it exists)</li>
90 * <li><b>my_key|agzyzz|rw</b>: will ask read-write access (if the default
92 * <li><b>my_key|agzyzz|wl|rw</b>: will ask both read-write access and white
97 * the key that will allow us to exchange information with the
100 * the host to contact or NULL for localhost
102 * the port to contact it on
104 public RemoteLibrary(String key
, String host
, int port
) {
107 index
= key
.indexOf('|');
111 this.key
= key
.substring(0, index
);
112 this.subkey
= key
.substring(index
+ 1);
118 if (host
.startsWith("fanfix://")) {
119 host
= host
.substring("fanfix://".length());
127 public String
getLibraryName() {
128 return (rw ?
"[READ-ONLY] " : "") + "fanfix://" + host
+ ":" + port
;
132 public Status
getStatus() {
133 Instance
.getInstance().getTraceHandler()
134 .trace("Getting remote lib status...");
135 Status status
= getStatusDo();
136 Instance
.getInstance().getTraceHandler()
137 .trace("Remote lib status: " + status
);
141 private Status
getStatusDo() {
142 final Status
[] result
= new Status
[1];
144 result
[0] = Status
.INVALID
;
147 new RemoteConnectAction() {
149 public void action(Version serverVersion
) throws Exception
{
150 Object rep
= send(new Object
[] { subkey
, "PING" });
152 if ("r/w".equals(rep
)) {
154 result
[0] = Status
.READ_WRITE
;
155 } else if ("r/o".equals(rep
)) {
157 result
[0] = Status
.READ_ONLY
;
159 result
[0] = Status
.UNAUTHORIZED
;
164 protected void onError(Exception e
) {
165 if (e
instanceof SSLException
) {
166 result
[0] = Status
.UNAUTHORIZED
;
168 result
[0] = Status
.UNAVAILABLE
;
172 } catch (UnknownHostException e
) {
173 result
[0] = Status
.INVALID
;
174 } catch (IllegalArgumentException e
) {
175 result
[0] = Status
.INVALID
;
176 } catch (Exception e
) {
177 result
[0] = Status
.UNAVAILABLE
;
184 public Image
getCover(final String luid
) throws IOException
{
185 final Image
[] result
= new Image
[1];
187 connectRemoteAction(new RemoteAction() {
189 public void action(ConnectActionClientObject action
)
192 .send(new Object
[] { subkey
, "GET_COVER", luid
});
193 result
[0] = (Image
) rep
;
201 public Image
getCustomSourceCover(final String source
) throws IOException
{
202 return getCustomCover(source
, "SOURCE");
206 public Image
getCustomAuthorCover(final String author
) throws IOException
{
207 return getCustomCover(author
, "AUTHOR");
210 // type: "SOURCE" or "AUTHOR"
211 private Image
getCustomCover(final String source
, final String type
)
213 final Image
[] result
= new Image
[1];
215 connectRemoteAction(new RemoteAction() {
217 public void action(ConnectActionClientObject action
)
219 Object rep
= action
.send(new Object
[] { subkey
,
220 "GET_CUSTOM_COVER", type
, source
});
221 result
[0] = (Image
) rep
;
229 public synchronized Story
getStory(final String luid
, Progress pg
)
231 final Progress pgF
= pg
;
232 final Story
[] result
= new Story
[1];
234 connectRemoteAction(new RemoteAction() {
236 public void action(ConnectActionClientObject action
)
244 .send(new Object
[] { subkey
, "GET_STORY", luid
});
246 MetaData meta
= null;
247 if (rep
instanceof MetaData
) {
248 meta
= (MetaData
) rep
;
249 if (meta
.getWords() <= Integer
.MAX_VALUE
) {
250 pg
.setMinMax(0, (int) meta
.getWords());
254 List
<Object
> list
= new ArrayList
<Object
>();
255 for (Object obj
= action
.send(null); obj
!= null; obj
= action
261 result
[0] = RemoteLibraryServer
.rebuildStory(list
);
270 public synchronized Story
save(final Story story
, final String luid
,
271 Progress pg
) throws IOException
{
273 final String
[] luidSaved
= new String
[1];
274 Progress pgSave
= new Progress();
275 Progress pgRefresh
= new Progress();
281 pg
.addProgress(pgSave
, 9);
282 pg
.addProgress(pgRefresh
, 1);
284 final Progress pgF
= pgSave
;
286 connectRemoteAction(new RemoteAction() {
288 public void action(ConnectActionClientObject action
)
291 if (story
.getMeta().getWords() <= Integer
.MAX_VALUE
) {
292 pg
.setMinMax(0, (int) story
.getMeta().getWords());
295 action
.send(new Object
[] { subkey
, "SAVE_STORY", luid
});
297 List
<Object
> list
= RemoteLibraryServer
.breakStory(story
);
298 for (Object obj
: list
) {
303 luidSaved
[0] = (String
) action
.send(null);
309 // because the meta changed:
310 MetaData meta
= getInfo(luidSaved
[0]);
311 if (story
.getMeta().getClass() != null) {
312 // If already available locally:
313 meta
.setCover(story
.getMeta().getCover());
316 meta
.setCover(getCover(meta
.getLuid()));
326 public synchronized void delete(final String luid
) throws IOException
{
327 connectRemoteAction(new RemoteAction() {
329 public void action(ConnectActionClientObject action
)
331 action
.send(new Object
[] { subkey
, "DELETE_STORY", luid
});
337 public void setSourceCover(final String source
, final String luid
)
339 setCover(source
, luid
, "SOURCE");
343 public void setAuthorCover(final String author
, final String luid
)
345 setCover(author
, luid
, "AUTHOR");
348 // type = "SOURCE" | "AUTHOR"
349 private void setCover(final String value
, final String luid
,
350 final String type
) throws IOException
{
351 connectRemoteAction(new RemoteAction() {
353 public void action(ConnectActionClientObject action
)
355 action
.send(new Object
[] { subkey
, "SET_COVER", type
, value
,
362 // Could work (more slowly) without it
363 public MetaData
imprt(final URL url
, Progress pg
) throws IOException
{
364 // Import the file locally if it is actually a file
366 if (url
== null || url
.getProtocol().equalsIgnoreCase("file")) {
367 return super.imprt(url
, pg
);
370 // Import it remotely if it is an URL
376 final Progress pgF
= pg
;
377 final String
[] luid
= new String
[1];
379 connectRemoteAction(new RemoteAction() {
381 public void action(ConnectActionClientObject action
)
385 Object rep
= action
.send(
386 new Object
[] { subkey
, "IMPORT", url
.toString() });
389 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
393 rep
= action
.send(null);
397 luid
[0] = (String
) rep
;
401 if (luid
[0] == null) {
402 throw new IOException("Remote failure");
406 return getInfo(luid
[0]);
410 // Could work (more slowly) without it
411 protected synchronized void changeSTA(final String luid
,
412 final String newSource
, final String newTitle
,
413 final String newAuthor
, Progress pg
) throws IOException
{
415 final Progress pgF
= pg
== null ?
new Progress() : pg
;
417 connectRemoteAction(new RemoteAction() {
419 public void action(ConnectActionClientObject action
)
423 Object rep
= action
.send(new Object
[] { subkey
, "CHANGE_STA",
424 luid
, newSource
, newTitle
, newAuthor
});
426 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
430 rep
= action
.send(null);
437 public File
getFile(final String luid
, Progress pg
) {
438 throw new java
.lang
.InternalError(
439 "Operation not supportorted on remote Libraries");
445 * @throws IOException
446 * in case of I/O errors
447 * @throws SSLException
448 * when the key was not accepted
450 public void exit() throws IOException
, SSLException
{
451 connectRemoteAction(new RemoteAction() {
453 public void action(ConnectActionClientObject action
)
455 action
.send(new Object
[] { subkey
, "EXIT" });
462 public MetaData
getInfo(String luid
) throws IOException
{
463 List
<MetaData
> metas
= getMetasList(luid
, null);
464 if (!metas
.isEmpty()) {
472 protected List
<MetaData
> getMetas(Progress pg
) throws IOException
{
473 return getMetasList("*", pg
);
477 protected void updateInfo(MetaData meta
) {
478 // Will be taken care of directly server side
482 protected void invalidateInfo(String luid
) {
483 // Will be taken care of directly server side
486 // The following methods are only used by Save and Delete in BasicLibrary:
489 protected int getNextId() {
490 throw new java
.lang
.InternalError("Should not have been called");
494 protected void doDelete(String luid
) throws IOException
{
495 throw new java
.lang
.InternalError("Should not have been called");
499 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
500 throw new java
.lang
.InternalError("Should not have been called");
506 * Return the meta of the given story or a list of all known metas if the
509 * Will not get the covers.
512 * the luid of the story or *
514 * the optional progress
518 * @throws IOException
519 * in case of I/O error or bad key (SSLException)
521 private List
<MetaData
> getMetasList(final String luid
, Progress pg
)
523 final Progress pgF
= pg
;
524 final List
<MetaData
> metas
= new ArrayList
<MetaData
>();
526 connectRemoteAction(new RemoteAction() {
528 public void action(ConnectActionClientObject action
)
536 .send(new Object
[] { subkey
, "GET_METADATA", luid
});
539 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
543 rep
= action
.send(null);
546 if (rep
instanceof MetaData
[]) {
547 for (MetaData meta
: (MetaData
[]) rep
) {
550 } else if (rep
!= null) {
551 metas
.add((MetaData
) rep
);
559 private void connectRemoteAction(final RemoteAction runAction
)
561 final IOException
[] err
= new IOException
[1];
563 final RemoteConnectAction
[] array
= new RemoteConnectAction
[1];
564 RemoteConnectAction ra
= new RemoteConnectAction() {
566 public void action(Version serverVersion
) throws Exception
{
567 runAction
.action(array
[0]);
571 protected void onError(Exception e
) {
572 if (!(e
instanceof IOException
)) {
573 Instance
.getInstance().getTraceHandler().error(e
);
577 err
[0] = (IOException
) e
;
582 } catch (Exception e
) {
583 err
[0] = (IOException
) e
;
586 if (err
[0] != null) {