1 package be
.nikiroo
.fanfix
.library
;
4 import java
.io
.FileInputStream
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
7 import java
.security
.KeyStore
;
8 import java
.util
.HashMap
;
12 import javax
.net
.ssl
.KeyManagerFactory
;
13 import javax
.net
.ssl
.SSLServerSocketFactory
;
15 import be
.nikiroo
.fanfix
.Instance
;
16 import be
.nikiroo
.fanfix
.bundles
.Config
;
17 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
18 import be
.nikiroo
.fanfix
.data
.Chapter
;
19 import be
.nikiroo
.fanfix
.data
.MetaData
;
20 import be
.nikiroo
.fanfix
.data
.Paragraph
;
21 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
22 import be
.nikiroo
.fanfix
.data
.Story
;
23 import be
.nikiroo
.fanfix
.library
.WebLibraryServer
.WLoginResult
;
24 import be
.nikiroo
.fanfix
.library
.web
.WebLibraryServerIndex
;
25 import be
.nikiroo
.fanfix
.reader
.TextOutput
;
26 import be
.nikiroo
.utils
.IOUtils
;
27 import be
.nikiroo
.utils
.NanoHTTPD
;
28 import be
.nikiroo
.utils
.NanoHTTPD
.IHTTPSession
;
29 import be
.nikiroo
.utils
.NanoHTTPD
.Response
;
30 import be
.nikiroo
.utils
.NanoHTTPD
.Response
.Status
;
31 import be
.nikiroo
.utils
.TraceHandler
;
32 import be
.nikiroo
.utils
.Version
;
34 abstract class WebLibraryServerHtml
implements Runnable
{
35 private NanoHTTPD server
;
36 protected TraceHandler tracer
= new TraceHandler();
38 abstract protected WLoginResult
login(String who
, String cookie
);
40 abstract protected WLoginResult
login(String who
, String key
,
43 abstract protected WLoginResult
login(boolean badLogin
, boolean badCookie
);
45 abstract protected Response
getList(String uri
, WLoginResult login
)
48 abstract protected Response
getStoryPart(String uri
, WLoginResult login
);
50 abstract protected Response
setStoryPart(String uri
, String value
,
51 WLoginResult login
) throws IOException
;
53 abstract protected Response
getCover(String uri
, WLoginResult login
)
56 abstract protected Response
setCover(String uri
, String luid
,
57 WLoginResult login
) throws IOException
;
59 abstract protected List
<MetaData
> metas(WLoginResult login
)
62 abstract protected Story
story(String luid
, WLoginResult login
)
65 protected abstract Response
imprt(String uri
, String url
,
66 WLoginResult login
) throws IOException
;
68 protected abstract Response
imprtProgress(String uri
, WLoginResult login
);
70 protected abstract Response
delete(String uri
, WLoginResult login
)
73 public WebLibraryServerHtml(boolean secure
) throws IOException
{
74 Integer port
= Instance
.getInstance().getConfig()
75 .getInteger(Config
.SERVER_PORT
);
77 throw new IOException(
78 "Cannot start web server: port not specified");
81 SSLServerSocketFactory ssf
= null;
83 String keystorePath
= Instance
.getInstance().getConfig()
84 .getString(Config
.SERVER_SSL_KEYSTORE
, "");
85 String keystorePass
= Instance
.getInstance().getConfig()
86 .getString(Config
.SERVER_SSL_KEYSTORE_PASS
);
88 if (secure
&& keystorePath
.isEmpty()) {
89 throw new IOException(
90 "Cannot start a secure web server: no keystore.jks file povided");
93 if (!keystorePath
.isEmpty()) {
94 File keystoreFile
= new File(keystorePath
);
96 KeyStore keystore
= KeyStore
97 .getInstance(KeyStore
.getDefaultType());
98 InputStream keystoreStream
= new FileInputStream(
101 keystore
.load(keystoreStream
,
102 keystorePass
.toCharArray());
103 KeyManagerFactory keyManagerFactory
= KeyManagerFactory
104 .getInstance(KeyManagerFactory
105 .getDefaultAlgorithm());
106 keyManagerFactory
.init(keystore
,
107 keystorePass
.toCharArray());
108 ssf
= NanoHTTPD
.makeSSLSocketFactory(keystore
,
111 keystoreStream
.close();
113 } catch (Exception e
) {
114 throw new IOException(e
.getMessage());
119 server
= new NanoHTTPD(port
) {
121 public Response
serve(final IHTTPSession session
) {
122 super.serve(session
);
124 String query
= session
.getQueryParameterString(); // a=a%20b&dd=2
125 Method method
= session
.getMethod(); // GET, POST..
126 String uri
= session
.getUri(); // /home.html
128 // need them in real time (not just those sent by the UA)
129 Map
<String
, String
> cookies
= new HashMap
<String
, String
>();
130 for (String cookie
: session
.getCookies()) {
131 cookies
.put(cookie
, session
.getCookies().read(cookie
));
134 WLoginResult login
= null;
135 Map
<String
, String
> params
= session
.getParms();
136 String who
= session
.getRemoteHostName()
137 + session
.getRemoteIpAddress();
138 if (params
.get("login") != null) {
139 login
= login(who
, params
.get("password"),
140 params
.get("login"));
142 String cookie
= cookies
.get("cookie");
143 login
= login(who
, cookie
);
146 if (login
.isSuccess()) {
148 session
.getCookies().set(new Cookie("cookie",
149 login
.getCookie(), "30; path=/"));
152 String optionName
= params
.get("optionName");
153 if (optionName
!= null && !optionName
.isEmpty()) {
154 String optionNo
= params
.get("optionNo");
155 String optionValue
= params
.get("optionValue");
156 if (optionNo
!= null || optionValue
== null
157 || optionValue
.isEmpty()) {
158 session
.getCookies().delete(optionName
);
159 cookies
.remove(optionName
);
161 session
.getCookies().set(new Cookie(optionName
,
162 optionValue
, "; path=/"));
163 cookies
.put(optionName
, optionValue
);
169 if (!login
.isSuccess() && WebLibraryUrls
.isSupportedUrl(uri
)) {
170 rep
= loginPage(login
, uri
);
175 if (WebLibraryUrls
.isSupportedUrl(uri
)) {
176 if (WebLibraryUrls
.INDEX_URL
.equals(uri
)) {
177 rep
= root(session
, cookies
, login
);
178 } else if (WebLibraryUrls
.VERSION_URL
.equals(uri
)) {
179 rep
= newFixedLengthResponse(Status
.OK
,
181 Version
.getCurrentVersion().toString());
182 } else if (WebLibraryUrls
.isCoverUrl(uri
)) {
183 String luid
= params
.get("luid");
185 rep
= setCover(uri
, luid
, login
);
187 rep
= getCover(uri
, login
);
189 } else if (WebLibraryUrls
.isListUrl(uri
)) {
190 rep
= getList(uri
, login
);
191 } else if (WebLibraryUrls
.isStoryUrl(uri
)) {
192 String value
= params
.get("value");
194 rep
= setStoryPart(uri
, value
, login
);
196 rep
= getStoryPart(uri
, login
);
198 } else if (WebLibraryUrls
.isViewUrl(uri
)) {
199 rep
= getViewer(cookies
, uri
, login
);
200 } else if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
201 session
.getCookies().delete("cookie");
202 cookies
.remove("cookie");
203 rep
= loginPage(login(false, false), uri
);
204 } else if (WebLibraryUrls
.isImprtUrl(uri
)) {
205 String url
= params
.get("url");
207 rep
= imprt(uri
, url
, login
);
209 rep
= imprtProgress(uri
, login
);
211 } else if (WebLibraryUrls
.isDeleteUrl(uri
)) {
212 rep
= delete(uri
, login
);
214 getTraceHandler().error(
215 "Supported URL was not processed: "
217 rep
= newFixedLengthResponse(
218 Status
.INTERNAL_ERROR
,
219 NanoHTTPD
.MIME_PLAINTEXT
,
220 "An error happened");
223 if (uri
.startsWith("/"))
224 uri
= uri
.substring(1);
225 InputStream in
= IOUtils
.openResource(
226 WebLibraryServerIndex
.class, uri
);
228 String mimeType
= MIME_PLAINTEXT
;
229 if (uri
.endsWith(".css")) {
230 mimeType
= "text/css";
231 } else if (uri
.endsWith(".html")) {
232 mimeType
= "text/html";
233 } else if (uri
.endsWith(".js")) {
234 mimeType
= "text/javascript";
236 rep
= newChunkedResponse(Status
.OK
, mimeType
,
241 getTraceHandler().trace("404: " + uri
);
242 rep
= newFixedLengthResponse(Status
.NOT_FOUND
,
243 NanoHTTPD
.MIME_PLAINTEXT
, "Not Found");
246 } catch (Exception e
) {
247 Instance
.getInstance().getTraceHandler().error(
248 new IOException("Cannot process web request",
250 rep
= newFixedLengthResponse(Status
.INTERNAL_ERROR
,
251 NanoHTTPD
.MIME_PLAINTEXT
, "An error occured");
260 getTraceHandler().trace("Install SSL on the web server...");
261 server
.makeSecure(ssf
, null);
262 getTraceHandler().trace("Done.");
269 server
.start(NanoHTTPD
.SOCKET_READ_TIMEOUT
, false);
270 } catch (IOException e
) {
271 tracer
.error(new IOException("Cannot start the web server", e
));
276 * The traces handler for this {@link WebLibraryServerHtml}.
278 * @return the traces handler
280 public TraceHandler
getTraceHandler() {
285 * The traces handler for this {@link WebLibraryServerHtml}.
288 * the new traces handler
290 public void setTraceHandler(TraceHandler tracer
) {
291 if (tracer
== null) {
292 tracer
= new TraceHandler(false, false, false);
295 this.tracer
= tracer
;
298 private Response
loginPage(WLoginResult login
, String uri
) {
299 StringBuilder builder
= new StringBuilder();
301 appendPreHtml(builder
, true);
303 if (login
.isBadLogin()) {
304 builder
.append("<div class='error'>Bad login or password</div>");
305 } else if (login
.isBadCookie()) {
306 builder
.append("<div class='error'>Your session timed out</div>");
309 if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
310 uri
= WebLibraryUrls
.INDEX_URL
;
314 "<form method='POST' action='" + uri
+ "' class='login'>\n");
316 "<p>You must be logged into the system to see the stories.</p>");
317 builder
.append("\t<input type='text' name='login' />\n");
318 builder
.append("\t<input type='password' name='password' />\n");
319 builder
.append("\t<input type='submit' value='Login' />\n");
320 builder
.append("</form>\n");
322 appendPostHtml(builder
);
324 return NanoHTTPD
.newFixedLengthResponse(Status
.FORBIDDEN
,
325 NanoHTTPD
.MIME_HTML
, builder
.toString());
328 private Response
root(IHTTPSession session
, Map
<String
, String
> cookies
,
329 WLoginResult login
) throws IOException
{
330 BasicLibrary lib
= Instance
.getInstance().getLibrary();
331 MetaResultList result
= new MetaResultList(metas(login
));
332 StringBuilder builder
= new StringBuilder();
334 appendPreHtml(builder
, true);
336 Map
<String
, String
> params
= session
.getParms();
338 String filter
= cookies
.get("filter");
339 if (params
.get("optionNo") != null)
341 if (filter
== null) {
345 String browser
= params
.get("browser") == null ?
""
346 : params
.get("browser");
347 String browser2
= params
.get("browser2") == null ?
""
348 : params
.get("browser2");
349 String browser3
= params
.get("browser3") == null ?
""
350 : params
.get("browser3");
352 String filterSource
= null;
353 String filterAuthor
= null;
354 String filterTag
= null;
356 // TODO: javascript in realtime, using visible=false + hide [submit]
358 builder
.append("<form class='browser'>\n");
359 builder
.append("<div class='breadcrumbs'>\n");
361 builder
.append("\t<select name='browser'>");
362 appendOption(builder
, 2, "", "", browser
);
363 appendOption(builder
, 2, "Sources", "sources", browser
);
364 appendOption(builder
, 2, "Authors", "authors", browser
);
365 appendOption(builder
, 2, "Tags", "tags", browser
);
366 builder
.append("\t</select>\n");
368 if (!browser
.isEmpty()) {
369 builder
.append("\t<select name='browser2'>");
370 if (browser
.equals("sources")) {
371 filterSource
= browser2
.isEmpty() ? filterSource
: browser2
;
372 // TODO: if 1 group -> no group
373 appendOption(builder
, 2, "", "", browser2
);
374 Map
<String
, List
<String
>> sources
= result
.getSourcesGrouped();
375 for (String source
: sources
.keySet()) {
376 appendOption(builder
, 2, source
, source
, browser2
);
378 } else if (browser
.equals("authors")) {
379 filterAuthor
= browser2
.isEmpty() ? filterAuthor
: browser2
;
380 // TODO: if 1 group -> no group
381 appendOption(builder
, 2, "", "", browser2
);
382 Map
<String
, List
<String
>> authors
= result
.getAuthorsGrouped();
383 for (String author
: authors
.keySet()) {
384 appendOption(builder
, 2, author
, author
, browser2
);
386 } else if (browser
.equals("tags")) {
387 filterTag
= browser2
.isEmpty() ? filterTag
: browser2
;
388 appendOption(builder
, 2, "", "", browser2
);
389 for (String tag
: result
.getTags()) {
390 appendOption(builder
, 2, tag
, tag
, browser2
);
393 builder
.append("\t</select>\n");
396 if (!browser2
.isEmpty()) {
397 if (browser
.equals("sources")) {
398 filterSource
= browser3
.isEmpty() ? filterSource
: browser3
;
399 Map
<String
, List
<String
>> sourcesGrouped
= result
400 .getSourcesGrouped();
401 List
<String
> sources
= sourcesGrouped
.get(browser2
);
402 if (sources
!= null && !sources
.isEmpty()) {
403 // TODO: single empty value
404 builder
.append("\t<select name='browser3'>");
405 appendOption(builder
, 2, "", "", browser3
);
406 for (String source
: sources
) {
407 appendOption(builder
, 2, source
, source
, browser3
);
409 builder
.append("\t</select>\n");
411 } else if (browser
.equals("authors")) {
412 filterAuthor
= browser3
.isEmpty() ? filterAuthor
: browser3
;
413 Map
<String
, List
<String
>> authorsGrouped
= result
414 .getAuthorsGrouped();
415 List
<String
> authors
= authorsGrouped
.get(browser2
);
416 if (authors
!= null && !authors
.isEmpty()) {
417 // TODO: single empty value
418 builder
.append("\t<select name='browser3'>");
419 appendOption(builder
, 2, "", "", browser3
);
420 for (String author
: authors
) {
421 appendOption(builder
, 2, author
, author
, browser3
);
423 builder
.append("\t</select>\n");
428 builder
.append("\t<input type='submit' value='Select'/>\n");
429 builder
.append("</div>\n");
431 // TODO: javascript in realtime, using visible=false + hide [submit]
432 builder
.append("<div class='filter'>\n");
433 builder
.append("\t<span class='label'>Filter: </span>\n");
435 "\t<input name='optionName' type='hidden' value='filter' />\n");
436 builder
.append("\t<input name='optionValue' type='text' value='"
437 + filter
+ "' place-holder='...' />\n");
438 builder
.append("\t<input name='optionNo' type='submit' value='x' />");
440 "\t<input name='submit' type='submit' value='Filter' />\n");
441 builder
.append("</div>\n");
442 builder
.append("</form>\n");
444 builder
.append("\t<div class='books'>");
445 for (MetaData meta
: result
.getMetas()) {
446 if (!filter
.isEmpty() && !meta
.getTitle().toLowerCase()
447 .contains(filter
.toLowerCase())) {
452 if (filterSource
!= null
453 && !filterSource
.equals(meta
.getSource())) {
458 if (filterAuthor
!= null
459 && !filterAuthor
.equals(meta
.getAuthor())) {
463 if (filterTag
!= null && !meta
.getTags().contains(filterTag
)) {
467 builder
.append("<div class='book_line'>");
468 builder
.append("<a href='");
470 WebLibraryUrls
.getViewUrl(meta
.getLuid(), null, null));
472 builder
.append(" class='link'>");
474 if (lib
.isCached(meta
.getLuid())) {
477 "<span class='cache_icon cached'>◉</span>");
481 "<span class='cache_icon uncached'>○</span>");
483 builder
.append("<span class='luid'>");
484 builder
.append(meta
.getLuid());
485 builder
.append("</span>");
486 builder
.append("<span class='title'>");
487 builder
.append(meta
.getTitle());
488 builder
.append("</span>");
489 builder
.append("<span class='author'>");
490 if (meta
.getAuthor() != null && !meta
.getAuthor().isEmpty()) {
491 builder
.append("(").append(meta
.getAuthor()).append(")");
493 builder
.append("</span>");
494 builder
.append("</a></div>\n");
496 builder
.append("</div>");
498 appendPostHtml(builder
);
499 return NanoHTTPD
.newFixedLengthResponse(builder
.toString());
502 private Response
getViewer(Map
<String
, String
> cookies
, String uri
,
503 WLoginResult login
) {
504 String
[] cover
= uri
.split("/");
507 if (cover
.length
< off
+ 2) {
508 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
509 NanoHTTPD
.MIME_PLAINTEXT
, null);
512 String type
= cover
[off
+ 0];
513 String luid
= cover
[off
+ 1];
514 String chapterStr
= cover
.length
< off
+ 3 ?
null : cover
[off
+ 2];
515 String paragraphStr
= cover
.length
< off
+ 4 ?
null : cover
[off
+ 3];
517 // 1-based (0 = desc)
519 if (chapterStr
!= null) {
521 chapter
= Integer
.parseInt(chapterStr
);
523 throw new NumberFormatException();
525 } catch (NumberFormatException e
) {
526 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
527 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter is not valid");
533 if (paragraphStr
!= null) {
535 paragraph
= Integer
.parseInt(paragraphStr
);
536 if (paragraph
<= 0) {
537 throw new NumberFormatException();
539 } catch (NumberFormatException e
) {
540 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
541 NanoHTTPD
.MIME_PLAINTEXT
, "Paragraph is not valid");
546 Story story
= story(luid
, login
);
548 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
549 NanoHTTPD
.MIME_PLAINTEXT
, "Story not found");
552 StringBuilder builder
= new StringBuilder();
553 appendPreHtml(builder
, false);
555 // For images documents, always go to the images if not chap 0 desc
556 if (story
.getMeta().isImageDocument()) {
557 if (chapter
> 0 && paragraph
<= 0)
563 chap
= story
.getMeta().getResume();
566 chap
= story
.getChapters().get(chapter
- 1);
567 } catch (IndexOutOfBoundsException e
) {
568 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
569 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter not found");
573 String first
, previous
, next
, last
;
575 StringBuilder content
= new StringBuilder();
577 String disabledLeft
= "";
578 String disabledRight
= "";
579 String disabledZoomReal
= "";
580 String disabledZoomWidth
= "";
581 String disabledZoomHeight
= "";
583 if (paragraph
<= 0) {
584 first
= WebLibraryUrls
.getViewUrl(luid
, 0, null);
585 previous
= WebLibraryUrls
.getViewUrl(luid
,
586 (Math
.max(chapter
- 1, 0)), null);
587 next
= WebLibraryUrls
.getViewUrl(luid
,
588 (Math
.min(chapter
+ 1, story
.getChapters().size())),
590 last
= WebLibraryUrls
.getViewUrl(luid
,
591 story
.getChapters().size(), null);
593 StringBuilder desc
= new StringBuilder();
596 desc
.append("<h1 class='title'>");
597 desc
.append(story
.getMeta().getTitle());
598 desc
.append("</h1>\n");
599 desc
.append("<div class='desc'>\n");
600 desc
.append("\t<a href='" + next
+ "' class='cover'>\n");
601 desc
.append("\t\t<img src='/story/" + luid
+ "/cover'/>\n");
602 desc
.append("\t</a>\n");
603 desc
.append("\t<table class='details'>\n");
604 Map
<String
, String
> details
= BasicLibrary
605 .getMetaDesc(story
.getMeta());
606 for (String key
: details
.keySet()) {
607 appendTableRow(desc
, 2, key
, details
.get(key
));
609 desc
.append("\t</table>\n");
610 desc
.append("</div>\n");
611 desc
.append("<h1 class='title'>Description</h1>\n");
614 content
.append("<div class='viewer text'>\n");
615 content
.append(desc
);
616 String description
= new TextOutput(false).convert(chap
,
618 content
.append(chap
.getParagraphs().size() <= 0
619 ?
"No content provided."
621 content
.append("</div>\n");
624 disabledLeft
= " disabled='disbaled'";
625 if (chapter
>= story
.getChapters().size())
626 disabledRight
= " disabled='disbaled'";
628 first
= WebLibraryUrls
.getViewUrl(luid
, chapter
, 1);
629 previous
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
630 (Math
.max(paragraph
- 1, 1)));
631 next
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
632 (Math
.min(paragraph
+ 1, chap
.getParagraphs().size())));
633 last
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
634 chap
.getParagraphs().size());
637 disabledLeft
= " disabled='disbaled'";
638 if (paragraph
>= chap
.getParagraphs().size())
639 disabledRight
= " disabled='disbaled'";
641 // First -> previous *chapter*
644 first
= WebLibraryUrls
.getViewUrl(luid
,
645 (Math
.max(chapter
- 1, 0)), null);
646 if (paragraph
<= 1) {
650 Paragraph para
= null;
652 para
= chap
.getParagraphs().get(paragraph
- 1);
653 } catch (IndexOutOfBoundsException e
) {
654 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
655 NanoHTTPD
.MIME_PLAINTEXT
,
656 "Paragraph " + paragraph
+ " not found");
659 if (para
.getType() == ParagraphType
.IMAGE
) {
660 String zoomStyle
= "max-width: 100%;";
661 disabledZoomWidth
= " disabled='disabled'";
662 String zoomOption
= cookies
.get("zoom");
663 if (zoomOption
!= null && !zoomOption
.isEmpty()) {
664 if (zoomOption
.equals("real")) {
666 disabledZoomWidth
= "";
667 disabledZoomReal
= " disabled='disabled'";
668 } else if (zoomOption
.equals("width")) {
669 zoomStyle
= "max-width: 100%;";
670 } else if (zoomOption
.equals("height")) {
671 // see height of navbar + optionbar
672 zoomStyle
= "max-height: calc(100% - 128px);";
673 disabledZoomWidth
= "";
674 disabledZoomHeight
= " disabled='disabled'";
678 String javascript
= "document.getElementById(\"previous\").click(); return false;";
679 content
.append(String
.format("" //
680 + "<a class='viewer link' oncontextmenu='%s' href='%s'>"
681 + "<img class='viewer img' style='%s' src='%s'/>"
686 WebLibraryUrls
.getStoryUrl(luid
, chapter
,
689 content
.append(String
.format("" //
690 + "<div class='viewer text'>%s</div>", //
695 builder
.append(String
.format("" //
696 + "<div class='bar navbar'>\n" //
697 + "\t<a%s class='button first' href='%s'><<</a>\n"//
698 + "\t<a%s id='previous' class='button previous' href='%s'><</a>\n" //
699 + "\t<div class='gotobox itemsbox'>\n" //
700 + "\t\t<div class='button goto'>%d</div>\n" //
701 + "\t\t<div class='items goto'>\n", //
702 disabledLeft
, first
, //
703 disabledLeft
, previous
, //
704 paragraph
> 0 ? paragraph
: chapter
//
707 // List of chap/para links
709 appendItemA(builder
, 3, WebLibraryUrls
.getViewUrl(luid
, 0, null),
710 "Description", paragraph
== 0 && chapter
== 0);
712 for (int i
= 1; i
<= chap
.getParagraphs().size(); i
++) {
713 appendItemA(builder
, 3,
714 WebLibraryUrls
.getViewUrl(luid
, chapter
, i
),
715 "Image " + i
, paragraph
== i
);
719 for (Chapter c
: story
.getChapters()) {
720 String chapName
= "Chapter " + c
.getNumber();
721 if (c
.getName() != null && !c
.getName().isEmpty()) {
722 chapName
+= ": " + c
.getName();
725 appendItemA(builder
, 3,
726 WebLibraryUrls
.getViewUrl(luid
, i
, null), chapName
,
733 builder
.append(String
.format("" //
736 + "\t<a%s class='button next' href='%s'>></a>\n" //
737 + "\t<a%s class='button last' href='%s'>>></a>\n"//
739 disabledRight
, next
, //
740 disabledRight
, last
//
743 builder
.append(content
);
745 builder
.append("<div class='bar optionbar ");
747 builder
.append("s4");
749 builder
.append("s1");
751 builder
.append("'>\n");
752 builder
.append(" <a class='button back' href='/'>BACK</a>\n");
755 builder
.append(String
.format("" //
756 + "\t<a%s class='button zoomreal' href='%s'>REAL</a>\n"//
757 + "\t<a%s class='button zoomwidth' href='%s'>WIDTH</a>\n"//
758 + "\t<a%s class='button zoomheight' href='%s'>HEIGHT</a>\n"//
761 uri
+ "?optionName=zoom&optionValue=real", //
763 uri
+ "?optionName=zoom&optionValue=width", //
765 uri
+ "?optionName=zoom&optionValue=height" //
769 appendPostHtml(builder
);
770 return NanoHTTPD
.newFixedLengthResponse(Status
.OK
,
771 NanoHTTPD
.MIME_HTML
, builder
.toString());
772 } catch (IOException e
) {
773 Instance
.getInstance().getTraceHandler()
774 .error(new IOException("Cannot get image: " + uri
, e
));
775 return NanoHTTPD
.newFixedLengthResponse(Status
.INTERNAL_ERROR
,
776 NanoHTTPD
.MIME_PLAINTEXT
, "Error when processing request");
780 protected Response
newInputStreamResponse(String mimeType
, InputStream in
) {
782 return NanoHTTPD
.newFixedLengthResponse(Status
.NO_CONTENT
, "",
785 return NanoHTTPD
.newChunkedResponse(Status
.OK
, mimeType
, in
);
788 private String
getContentOf(String file
) {
789 InputStream in
= IOUtils
.openResource(WebLibraryServerIndex
.class,
793 return IOUtils
.readSmallStream(in
);
794 } catch (IOException e
) {
795 Instance
.getInstance().getTraceHandler().error(
796 new IOException("Cannot get file: index.pre.html", e
));
803 private void appendPreHtml(StringBuilder builder
, boolean banner
) {
804 String favicon
= "favicon.ico";
805 String icon
= Instance
.getInstance().getUiConfig()
806 .getString(UiConfig
.PROGRAM_ICON
);
808 favicon
= "icon_" + icon
.replace("-", "_") + ".png";
812 getContentOf("index.pre.html").replace("favicon.ico", favicon
));
815 builder
.append("<div class='banner'>\n");
816 builder
.append("\t<img class='ico' src='/") //
819 builder
.append("\t<h1>Fanfix</h1>\n");
820 builder
.append("\t<h2>") //
821 .append(Version
.getCurrentVersion()) //
823 builder
.append("</div>\n");
827 private void appendPostHtml(StringBuilder builder
) {
828 builder
.append(getContentOf("index.post.html"));
831 private void appendOption(StringBuilder builder
, int depth
, String name
,
832 String value
, String selected
) {
833 for (int i
= 0; i
< depth
; i
++) {
834 builder
.append("\t");
836 builder
.append("<option value='").append(value
).append("'");
837 if (value
.equals(selected
)) {
838 builder
.append(" selected='selected'");
840 builder
.append(">").append(name
).append("</option>\n");
843 private void appendTableRow(StringBuilder builder
, int depth
,
845 for (int i
= 0; i
< depth
; i
++) {
846 builder
.append("\t");
850 builder
.append("<tr>");
851 for (String td
: tds
) {
852 builder
.append("<td class='col");
853 builder
.append(col
++);
854 builder
.append("'>");
856 builder
.append("</td>");
858 builder
.append("</tr>\n");
861 private void appendItemA(StringBuilder builder
, int depth
, String link
,
862 String name
, boolean selected
) {
863 for (int i
= 0; i
< depth
; i
++) {
864 builder
.append("\t");
867 builder
.append("<a href='");
868 builder
.append(link
);
869 builder
.append("' class='item goto");
871 builder
.append(" selected");
873 builder
.append("'>");
874 builder
.append(name
);
875 builder
.append("</a>\n");