1 package be
.nikiroo
.fanfix
.library
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
7 import java
.util
.ArrayList
;
8 import java
.util
.HashMap
;
12 import org
.json
.JSONArray
;
13 import org
.json
.JSONObject
;
15 import be
.nikiroo
.fanfix
.Instance
;
16 import be
.nikiroo
.fanfix
.data
.Chapter
;
17 import be
.nikiroo
.fanfix
.data
.JsonIO
;
18 import be
.nikiroo
.fanfix
.data
.MetaData
;
19 import be
.nikiroo
.fanfix
.data
.Paragraph
;
20 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
21 import be
.nikiroo
.fanfix
.data
.Story
;
22 import be
.nikiroo
.utils
.IOUtils
;
23 import be
.nikiroo
.utils
.Image
;
24 import be
.nikiroo
.utils
.Progress
;
25 import be
.nikiroo
.utils
.Version
;
28 * This {@link BasicLibrary} will access a remote server to list the available
29 * stories, and download the ones you try to load to the local directory
30 * specified in the configuration.
32 * This remote library uses http:// or https://.
36 public class WebLibrary
extends BasicLibrary
{
39 private final String key
;
40 private final String subkey
;
42 // informative only (server will make the actual checks)
46 * Create a {@link RemoteLibrary} linked to the given server.
48 * Note that the key is structured:
49 * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>|<b>wl</b>)(|<b>rw</b>)</tt>
51 * Note that anything before the first pipe (<tt>|</tt>) character is
52 * considered to be the encryption key, anything after that character is
53 * called the subkey (including the other pipe characters and flags!).
55 * This is important because the subkey (including the pipe characters and
56 * flags) must be present as-is in the server configuration file to be
59 * <li><b><i>xxx</i></b>: the encryption key used to communicate with the
61 * <li><b><i>yyy</i></b>: the secondary key</li>
62 * <li><b>rw</b>: flag to allow read and write access if it is not the
63 * default on this server</li>
64 * <li><b>wl</b>: flag to allow access to all the stories (bypassing the
65 * whitelist if it exists)</li>
70 * <li><b>my_key</b>: normal connection, will take the default server
72 * <li><b>my_key|agzyzz|wl</b>: will ask to bypass the white list (if it
74 * <li><b>my_key|agzyzz|rw</b>: will ask read-write access (if the default
76 * <li><b>my_key|agzyzz|wl|rw</b>: will ask both read-write access and white
81 * the key that will allow us to exchange information with the
84 * the host to contact or NULL for localhost
86 * the port to contact it on
88 public WebLibrary(String key
, String host
, int port
) {
91 index
= key
.indexOf('|');
95 this.key
= key
.substring(0, index
);
96 this.subkey
= key
.substring(index
+ 1);
102 this.rw
= subkey
.contains("|rw");
109 * Return the version of the program running server-side.
111 * Never returns NULL.
113 * @return the version or an empty {@link Version} if not known
115 public Version
getVersion() {
117 InputStream in
= post(WebLibraryUrls
.VERSION_URL
);
119 return new Version(IOUtils
.readSmallStream(in
));
123 } catch (IOException e
) {
126 return new Version();
132 * @throws IOException
133 * in case of I/O errors
135 public void stop() throws IOException
{
137 post(WebLibraryUrls
.EXIT_URL
, null).close();
138 } catch (Exception e
) {
141 } catch (InterruptedException e1
) {
143 if (getStatus() != Status
.UNAVAILABLE
) {
144 throw new IOException("Cannot exit the library", e
);
150 public Status
getStatus() {
152 post(WebLibraryUrls
.INDEX_URL
).close();
153 } catch (IOException e
) {
155 post("/style.css").close();
156 return Status
.UNAUTHORIZED
;
157 } catch (IOException ioe
) {
158 return Status
.UNAVAILABLE
;
162 return rw ? Status
.READ_WRITE
: Status
.READ_ONLY
;
166 public String
getLibraryName() {
167 return (rw ?
"[READ-ONLY] " : "") + host
+ ":" + port
+ " ("
168 + getVersion() + ")";
172 public Image
getCover(String luid
) throws IOException
{
173 InputStream in
= post(WebLibraryUrls
.getStoryUrlCover(luid
));
175 Image img
= new Image(in
);
176 if (img
.getSize() > 0) {
187 public Image
getCustomSourceCover(String source
) throws IOException
{
188 InputStream in
= post(WebLibraryUrls
.getCoverUrlSource(source
));
190 Image img
= new Image(in
);
191 if (img
.getSize() > 0) {
202 public Image
getCustomAuthorCover(String author
) throws IOException
{
203 InputStream in
= post(WebLibraryUrls
.getCoverUrlAuthor(author
));
205 Image img
= new Image(in
);
206 if (img
.getSize() > 0) {
217 public void setSourceCover(String source
, String luid
) throws IOException
{
218 Map
<String
, String
> post
= new HashMap
<String
, String
>();
219 post
.put("luid", luid
);
220 post(WebLibraryUrls
.getCoverUrlSource(source
), post
).close();
224 public void setAuthorCover(String author
, String luid
) throws IOException
{
225 Map
<String
, String
> post
= new HashMap
<String
, String
>();
226 post
.put("luid", luid
);
227 post(WebLibraryUrls
.getCoverUrlAuthor(author
), post
).close();
231 public synchronized Story
getStory(final String luid
, Progress pg
)
238 InputStream in
= post(WebLibraryUrls
.getStoryUrlJson(luid
));
240 JSONObject json
= new JSONObject(IOUtils
.readSmallStream(in
));
241 story
= JsonIO
.toStory(json
);
247 for (Chapter chap
: story
) {
248 max
+= chap
.getParagraphs().size();
250 pg
.setMinMax(0, max
);
252 story
.getMeta().setCover(getCover(luid
));
254 for (Chapter chap
: story
) {
256 for (Paragraph para
: chap
) {
257 if (para
.getType() == ParagraphType
.IMAGE
) {
258 InputStream subin
= post(
259 WebLibraryUrls
.getStoryUrl(luid
, chapNum
, number
));
261 Image img
= new Image(subin
);
262 if (img
.getSize() > 0) {
263 para
.setContentImage(img
);
282 protected List
<MetaData
> getMetas(Progress pg
) throws IOException
{
283 List
<MetaData
> metas
= new ArrayList
<MetaData
>();
284 InputStream in
= post(WebLibraryUrls
.LIST_URL_METADATA
);
285 JSONArray jsonArr
= new JSONArray(IOUtils
.readSmallStream(in
));
286 for (int i
= 0; i
< jsonArr
.length(); i
++) {
287 JSONObject json
= jsonArr
.getJSONObject(i
);
288 metas
.add(JsonIO
.toMetaData(json
));
295 // Could work (more slowly) without it
296 public MetaData
imprt(final URL url
, Progress pg
) throws IOException
{
301 // Import the file locally if it is actually a file
303 if (url
== null || url
.getProtocol().equalsIgnoreCase("file")) {
304 return super.imprt(url
, pg
);
307 // Import it remotely if it is an URL
312 Map
<String
, String
> post
= new HashMap
<String
, String
>();
313 post
.put("url", url
.toString());
314 InputStream in
= post(WebLibraryUrls
.IMPRT_URL_IMPORT
, post
);
316 luid
= IOUtils
.readSmallStream(in
);
321 Progress subPg
= null;
325 } catch (InterruptedException e
) {
328 in
= post(WebLibraryUrls
.getImprtProgressUrl(luid
));
330 subPg
= JsonIO
.toProgress(
331 new JSONObject(IOUtils
.readSmallStream(in
)));
332 pg
.setName(subPg
.getName());
333 pg
.setMinMax(subPg
.getMin(), subPg
.getMax());
334 pg
.setProgress(subPg
.getProgress());
335 } catch (Exception e
) {
340 } while (subPg
!= null);
342 in
= post(WebLibraryUrls
.getStoryUrlMetadata(luid
));
344 return JsonIO
.toMetaData(
345 new JSONObject(IOUtils
.readSmallStream(in
)));
355 // Could work (more slowly) without it
356 protected synchronized void changeSTA(final String luid
,
357 final String newSource
, final String newTitle
,
358 final String newAuthor
, Progress pg
) throws IOException
{
359 MetaData meta
= getInfo(luid
);
361 if (!meta
.getSource().equals(newSource
)) {
362 Map
<String
, String
> post
= new HashMap
<String
, String
>();
363 post
.put("value", newSource
);
364 post(WebLibraryUrls
.getStoryUrlSource(luid
), post
).close();
366 if (!meta
.getTitle().equals(newTitle
)) {
367 Map
<String
, String
> post
= new HashMap
<String
, String
>();
368 post
.put("value", newTitle
);
369 post(WebLibraryUrls
.getStoryUrlTitle(luid
), post
).close();
371 if (!meta
.getAuthor().equals(newAuthor
)) {
372 Map
<String
, String
> post
= new HashMap
<String
, String
>();
373 post
.put("value", newAuthor
);
374 post(WebLibraryUrls
.getStoryUrlAuthor(luid
), post
).close();
380 public synchronized void delete(String luid
) throws IOException
{
381 post(WebLibraryUrls
.getDeleteUrlStory(luid
), null).close();
385 protected void updateInfo(MetaData meta
) {
386 // Will be taken care of directly server side
390 protected void invalidateInfo(String luid
) {
391 // Will be taken care of directly server side
394 // The following methods are only used by Save and Delete in BasicLibrary:
397 protected String
getNextId() {
398 throw new java
.lang
.InternalError("Should not have been called");
402 protected void doDelete(String luid
) throws IOException
{
403 throw new java
.lang
.InternalError("Should not have been called");
407 protected Story
doSave(Story story
, Progress pg
) throws IOException
{
408 throw new java
.lang
.InternalError("Should not have been called");
414 public File
getFile(final String luid
, Progress pg
) {
415 throw new java
.lang
.InternalError(
416 "Operation not supportorted on remote Libraries");
419 // starts with "/", never NULL
420 private InputStream
post(String path
) throws IOException
{
421 return post(path
, null);
424 // starts with "/", never NULL
425 private InputStream
post(String path
, Map
<String
, String
> post
)
427 URL url
= new URL(host
+ ":" + port
+ path
);
430 post
= new HashMap
<String
, String
>();
432 post
.put("login", subkey
);
433 post
.put("password", key
);
435 return Instance
.getInstance().getCache().openNoCache(url
, null, post
,