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.
27 public class RemoteLibrary
extends BasicLibrary
{
28 interface RemoteAction
{
29 public void action(ConnectActionClientObject action
) throws Exception
;
32 class RemoteConnectAction
extends ConnectActionClientObject
{
33 public RemoteConnectAction() throws IOException
{
34 super(host
, port
, key
);
38 public Object
send(Object data
) throws IOException
,
39 NoSuchFieldException
, NoSuchMethodException
,
40 ClassNotFoundException
{
41 Object rep
= super.send(data
);
42 if (rep
instanceof RemoteLibraryException
) {
43 RemoteLibraryException remoteEx
= (RemoteLibraryException
) rep
;
44 IOException cause
= remoteEx
.getCause();
46 cause
= new IOException("IOException");
58 private final String key
;
59 private final String subkey
;
61 // informative only (server will make the actual checks)
65 * Create a {@link RemoteLibrary} linked to the given server.
67 * Note that the key is structured:
68 * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>|<b>wl</b>)(|<b>rw</b>)</tt>
70 * Note that anything before the first pipe (<tt>|</tt>) character is
71 * considered to be the encryption key, anything after that character is
72 * called the subkey (including the other pipe characters and flags!).
74 * This is important because the subkey (including the pipe characters and
75 * flags) must be present as-is in the server configuration file to be
78 * <li><b><i>xxx</i></b>: the encryption key used to communicate with the
80 * <li><b><i>yyy</i></b>: the secondary key</li>
81 * <li><b>rw</b>: flag to allow read and write access if it is not the
82 * default on this server</li>
83 * <li><b>wl</b>: flag to allow access to all the stories (bypassing the
84 * whitelist if it exists)</li>
89 * <li><b>my_key</b>: normal connection, will take the default server
91 * <li><b>my_key|agzyzz|wl</b>: will ask to bypass the white list (if it
93 * <li><b>my_key|agzyzz|rw</b>: will ask read-write access (if the default
95 * <li><b>my_key|agzyzz|wl|rw</b>: will ask both read-write access and white
100 * the key that will allow us to exchange information with the
103 * the host to contact or NULL for localhost
105 * the port to contact it on
107 public RemoteLibrary(String key
, String host
, int port
) {
110 index
= key
.indexOf('|');
114 this.key
= key
.substring(0, index
);
115 this.subkey
= key
.substring(index
+ 1);
126 public String
getLibraryName() {
127 return (rw ?
"[READ-ONLY] " : "") + host
+ ":" + port
;
131 public Status
getStatus() {
132 Instance
.getTraceHandler().trace("Getting remote lib status...");
133 Status status
= getStatusDo();
134 Instance
.getTraceHandler().trace("Remote lib status: " + status
);
138 private Status
getStatusDo() {
139 final Status
[] result
= new Status
[1];
141 result
[0] = Status
.INVALID
;
144 new RemoteConnectAction() {
146 public void action(Version serverVersion
) throws Exception
{
147 Object rep
= send(new Object
[] { subkey
, "PING" });
149 if ("r/w".equals(rep
)) {
151 result
[0] = Status
.READ_WRITE
;
152 } else if ("r/o".equals(rep
)) {
154 result
[0] = Status
.READ_ONLY
;
156 result
[0] = Status
.UNAUTHORIZED
;
161 protected void onError(Exception e
) {
162 if (e
instanceof SSLException
) {
163 result
[0] = Status
.UNAUTHORIZED
;
165 result
[0] = Status
.UNAVAILABLE
;
169 } catch (UnknownHostException e
) {
170 result
[0] = Status
.INVALID
;
171 } catch (IllegalArgumentException e
) {
172 result
[0] = Status
.INVALID
;
173 } catch (Exception e
) {
174 result
[0] = Status
.UNAVAILABLE
;
181 public Image
getCover(final String luid
) throws IOException
{
182 final Image
[] result
= new Image
[1];
184 connectRemoteAction(new RemoteAction() {
186 public void action(ConnectActionClientObject action
)
188 Object rep
= action
.send(new Object
[] { subkey
, "GET_COVER",
190 result
[0] = (Image
) rep
;
198 public Image
getCustomSourceCover(final String source
) throws IOException
{
199 return getCustomCover(source
, "SOURCE");
203 public Image
getCustomAuthorCover(final String author
) throws IOException
{
204 return getCustomCover(author
, "AUTHOR");
207 // type: "SOURCE" or "AUTHOR"
208 private Image
getCustomCover(final String source
, final String type
)
210 final Image
[] result
= new Image
[1];
212 connectRemoteAction(new RemoteAction() {
214 public void action(ConnectActionClientObject action
)
216 Object rep
= action
.send(new Object
[] { subkey
,
217 "GET_CUSTOM_COVER", type
, source
});
218 result
[0] = (Image
) rep
;
226 public synchronized Story
getStory(final String luid
, Progress pg
)
228 final Progress pgF
= pg
;
229 final Story
[] result
= new Story
[1];
231 connectRemoteAction(new RemoteAction() {
233 public void action(ConnectActionClientObject action
)
240 Object rep
= action
.send(new Object
[] { subkey
, "GET_STORY",
243 MetaData meta
= null;
244 if (rep
instanceof MetaData
) {
245 meta
= (MetaData
) rep
;
246 if (meta
.getWords() <= Integer
.MAX_VALUE
) {
247 pg
.setMinMax(0, (int) meta
.getWords());
251 List
<Object
> list
= new ArrayList
<Object
>();
252 for (Object obj
= action
.send(null); obj
!= null; obj
= action
258 result
[0] = RemoteLibraryServer
.rebuildStory(list
);
267 public synchronized Story
save(final Story story
, final String luid
,
268 Progress pg
) throws IOException
{
270 final String
[] luidSaved
= new String
[1];
271 Progress pgSave
= new Progress();
272 Progress pgRefresh
= new Progress();
278 pg
.addProgress(pgSave
, 9);
279 pg
.addProgress(pgRefresh
, 1);
281 final Progress pgF
= pgSave
;
283 connectRemoteAction(new RemoteAction() {
285 public void action(ConnectActionClientObject action
)
288 if (story
.getMeta().getWords() <= Integer
.MAX_VALUE
) {
289 pg
.setMinMax(0, (int) story
.getMeta().getWords());
292 action
.send(new Object
[] { subkey
, "SAVE_STORY", luid
});
294 List
<Object
> list
= RemoteLibraryServer
.breakStory(story
);
295 for (Object obj
: list
) {
300 luidSaved
[0] = (String
) action
.send(null);
306 // because the meta changed:
307 MetaData meta
= getInfo(luidSaved
[0]);
308 if (story
.getMeta().getClass() != null) {
309 // If already available locally:
310 meta
.setCover(story
.getMeta().getCover());
313 meta
.setCover(getCover(meta
.getLuid()));
323 public synchronized void delete(final String luid
) throws IOException
{
324 connectRemoteAction(new RemoteAction() {
326 public void action(ConnectActionClientObject action
)
328 action
.send(new Object
[] { subkey
, "DELETE_STORY", luid
});
334 public void setSourceCover(final String source
, final String luid
)
336 setCover(source
, luid
, "SOURCE");
340 public void setAuthorCover(final String author
, final String luid
)
342 setCover(author
, luid
, "AUTHOR");
345 // type = "SOURCE" | "AUTHOR"
346 private void setCover(final String value
, final String luid
,
347 final String type
) throws IOException
{
348 connectRemoteAction(new RemoteAction() {
350 public void action(ConnectActionClientObject action
)
352 action
.send(new Object
[] { subkey
, "SET_COVER", type
, value
,
359 // Could work (more slowly) without it
360 public Story
imprt(final URL url
, Progress pg
) throws IOException
{
361 // Import the file locally if it is actually a file
362 if (url
== null || url
.getProtocol().equalsIgnoreCase("file")) {
363 return super.imprt(url
, pg
);
366 // Import it remotely if it is an URL
373 Progress pgImprt
= new Progress();
374 Progress pgGet
= new Progress();
375 pg
.addProgress(pgImprt
, 1);
376 pg
.addProgress(pgGet
, 1);
378 final Progress pgF
= pgImprt
;
379 final String
[] luid
= new String
[1];
381 connectRemoteAction(new RemoteAction() {
383 public void action(ConnectActionClientObject action
)
387 Object rep
= action
.send(new Object
[] { subkey
, "IMPORT",
391 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
395 rep
= action
.send(null);
399 luid
[0] = (String
) rep
;
403 if (luid
[0] == null) {
404 throw new IOException("Remote failure");
407 Story story
= getStory(luid
[0], pgGet
);
415 // Could work (more slowly) without it
416 protected synchronized void changeSTA(final String luid
,
417 final String newSource
, final String newTitle
,
418 final String newAuthor
, Progress pg
) throws IOException
{
420 final Progress pgF
= pg
== null ?
new Progress() : pg
;
422 connectRemoteAction(new RemoteAction() {
424 public void action(ConnectActionClientObject action
)
428 Object rep
= action
.send(new Object
[] { subkey
, "CHANGE_STA",
429 luid
, newSource
, newTitle
, newAuthor
});
431 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
435 rep
= action
.send(null);
442 public synchronized File
getFile(final String luid
, Progress pg
) {
443 throw new java
.lang
.InternalError(
444 "Operation not supportorted on remote Libraries");
450 public void exit() throws IOException
{
451 connectRemoteAction(new RemoteAction() {
453 public void action(ConnectActionClientObject action
)
455 action
.send(new Object
[] { subkey
, "EXIT" });
461 public synchronized MetaData
getInfo(String luid
) throws IOException
{
462 List
<MetaData
> metas
= getMetasList(luid
, null);
463 if (!metas
.isEmpty()) {
471 protected List
<MetaData
> getMetas(Progress pg
) throws IOException
{
472 return getMetasList("*", pg
);
476 protected void updateInfo(MetaData meta
) {
477 // Will be taken care of directly server side
481 protected void invalidateInfo(String luid
) {
482 // Will be taken care of directly server side
485 // The following methods are only used by Save and Delete in BasicLibrary:
488 protected int getNextId() {
489 throw new java
.lang
.InternalError("Should not have been called");
493 protected void doDelete(String luid
) throws IOException
{
494 throw new java
.lang
.InternalError("Should not have been called");
498 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
499 throw new java
.lang
.InternalError("Should not have been called");
505 * Return the meta of the given story or a list of all known metas if the
508 * Will not get the covers.
511 * the luid of the story or *
513 * the optional progress
517 * @throws IOException
518 * in case of I/O error or bad key (SSLException)
520 private List
<MetaData
> getMetasList(final String luid
, Progress pg
)
522 final Progress pgF
= pg
;
523 final List
<MetaData
> metas
= new ArrayList
<MetaData
>();
525 connectRemoteAction(new RemoteAction() {
527 public void action(ConnectActionClientObject action
)
534 Object rep
= action
.send(new Object
[] { subkey
, "GET_METADATA",
538 if (!RemoteLibraryServer
.updateProgress(pg
, rep
)) {
542 rep
= action
.send(null);
545 if (rep
instanceof MetaData
[]) {
546 for (MetaData meta
: (MetaData
[]) rep
) {
549 } else if (rep
!= null) {
550 metas
.add((MetaData
) rep
);
558 private void connectRemoteAction(final RemoteAction runAction
)
560 final IOException
[] err
= new IOException
[1];
562 final RemoteConnectAction
[] array
= new RemoteConnectAction
[1];
563 RemoteConnectAction ra
= new RemoteConnectAction() {
565 public void action(Version serverVersion
) throws Exception
{
566 runAction
.action(array
[0]);
570 protected void onError(Exception e
) {
571 if (!(e
instanceof IOException
)) {
572 Instance
.getTraceHandler().error(e
);
576 err
[0] = (IOException
) e
;
581 } catch (Exception e
) {
582 err
[0] = (IOException
) e
;
585 if (err
[0] != null) {