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
.ArrayList
;
9 import java
.util
.Arrays
;
10 import java
.util
.HashMap
;
11 import java
.util
.List
;
14 import javax
.net
.ssl
.KeyManagerFactory
;
15 import javax
.net
.ssl
.SSLServerSocketFactory
;
17 import be
.nikiroo
.fanfix
.Instance
;
18 import be
.nikiroo
.fanfix
.bundles
.Config
;
19 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
20 import be
.nikiroo
.fanfix
.data
.Chapter
;
21 import be
.nikiroo
.fanfix
.data
.MetaData
;
22 import be
.nikiroo
.fanfix
.data
.Paragraph
;
23 import be
.nikiroo
.fanfix
.data
.Paragraph
.ParagraphType
;
24 import be
.nikiroo
.fanfix
.data
.Story
;
25 import be
.nikiroo
.fanfix
.library
.WebLibraryServer
.WLoginResult
;
26 import be
.nikiroo
.fanfix
.library
.web
.WebLibraryServerIndex
;
27 import be
.nikiroo
.fanfix
.library
.web
.templates
.WebLibraryServerTemplates
;
28 import be
.nikiroo
.fanfix
.reader
.TextOutput
;
29 import be
.nikiroo
.utils
.IOUtils
;
30 import be
.nikiroo
.utils
.NanoHTTPD
;
31 import be
.nikiroo
.utils
.NanoHTTPD
.IHTTPSession
;
32 import be
.nikiroo
.utils
.NanoHTTPD
.Response
;
33 import be
.nikiroo
.utils
.NanoHTTPD
.Response
.Status
;
34 import be
.nikiroo
.utils
.TraceHandler
;
35 import be
.nikiroo
.utils
.Version
;
37 abstract class WebLibraryServerHtml
implements Runnable
{
38 private NanoHTTPD server
;
39 protected TraceHandler tracer
= new TraceHandler();
41 WebLibraryServerTemplates templates
= WebLibraryServerTemplates
44 abstract protected WLoginResult
login(String who
, String cookie
);
46 abstract protected WLoginResult
login(String who
, String key
,
49 abstract protected WLoginResult
login(boolean badLogin
, boolean badCookie
);
51 abstract protected Response
getList(String uri
, WLoginResult login
)
54 abstract protected Response
getStoryPart(String uri
, WLoginResult login
);
56 abstract protected Response
setStoryPart(String uri
, String value
,
57 WLoginResult login
) throws IOException
;
59 abstract protected Response
getCover(String uri
, WLoginResult login
)
62 abstract protected Response
setCover(String uri
, String luid
,
63 WLoginResult login
) throws IOException
;
65 abstract protected List
<MetaData
> metas(WLoginResult login
)
68 abstract protected Story
story(String luid
, WLoginResult login
)
71 protected abstract Response
imprt(String uri
, String url
,
72 WLoginResult login
) throws IOException
;
74 protected abstract Response
imprtProgress(String uri
, WLoginResult login
);
76 protected abstract Response
delete(String uri
, WLoginResult login
)
80 * Wait until all operations are done and stop the server.
82 * All the new R/W operations will be refused after a call to stop.
84 protected abstract Response
stop(WLoginResult login
);
86 public WebLibraryServerHtml(boolean secure
) throws IOException
{
87 Integer port
= Instance
.getInstance().getConfig()
88 .getInteger(Config
.SERVER_PORT
);
90 throw new IOException(
91 "Cannot start web server: port not specified");
94 SSLServerSocketFactory ssf
= null;
96 String keystorePath
= Instance
.getInstance().getConfig()
97 .getString(Config
.SERVER_SSL_KEYSTORE
, "");
98 String keystorePass
= Instance
.getInstance().getConfig()
99 .getString(Config
.SERVER_SSL_KEYSTORE_PASS
);
101 if (secure
&& keystorePath
.isEmpty()) {
102 throw new IOException(
103 "Cannot start a secure web server: no keystore.jks file povided");
106 if (!keystorePath
.isEmpty()) {
107 File keystoreFile
= new File(keystorePath
);
109 KeyStore keystore
= KeyStore
110 .getInstance(KeyStore
.getDefaultType());
111 InputStream keystoreStream
= new FileInputStream(
114 keystore
.load(keystoreStream
,
115 keystorePass
.toCharArray());
116 KeyManagerFactory keyManagerFactory
= KeyManagerFactory
117 .getInstance(KeyManagerFactory
118 .getDefaultAlgorithm());
119 keyManagerFactory
.init(keystore
,
120 keystorePass
.toCharArray());
121 ssf
= NanoHTTPD
.makeSSLSocketFactory(keystore
,
124 keystoreStream
.close();
126 } catch (Exception e
) {
127 throw new IOException(e
.getMessage());
132 server
= new NanoHTTPD(port
) {
134 public Response
serve(final IHTTPSession session
) {
135 super.serve(session
);
137 String query
= session
.getQueryParameterString(); // a=a%20b&dd=2
138 Method method
= session
.getMethod(); // GET, POST..
139 String uri
= session
.getUri(); // /home.html
141 // need them in real time (not just those sent by the UA)
142 Map
<String
, String
> cookies
= new HashMap
<String
, String
>();
143 for (String cookie
: session
.getCookies()) {
144 cookies
.put(cookie
, session
.getCookies().read(cookie
));
147 WLoginResult login
= null;
148 Map
<String
, String
> params
= session
.getParms();
149 String who
= session
.getRemoteHostName()
150 + session
.getRemoteIpAddress();
151 if (params
.get("login") != null) {
152 login
= login(who
, params
.get("password"),
153 params
.get("login"));
155 String cookie
= cookies
.get("cookie");
156 login
= login(who
, cookie
);
159 if (login
.isSuccess()) {
161 session
.getCookies().set(new Cookie("cookie",
162 login
.getCookie(), "30; path=/"));
165 String optionName
= params
.get("optionName");
166 if (optionName
!= null && !optionName
.isEmpty()) {
167 String optionNo
= params
.get("optionNo");
168 String optionValue
= params
.get("optionValue");
169 if (optionNo
!= null || optionValue
== null
170 || optionValue
.isEmpty()) {
171 session
.getCookies().delete(optionName
);
172 cookies
.remove(optionName
);
174 session
.getCookies().set(new Cookie(optionName
,
175 optionValue
, "; path=/"));
176 cookies
.put(optionName
, optionValue
);
183 if (!login
.isSuccess()
184 && WebLibraryUrls
.isSupportedUrl(uri
, true)) {
185 rep
= loginPage(login
, uri
);
189 if (WebLibraryUrls
.isSupportedUrl(uri
, false)) {
190 if (WebLibraryUrls
.INDEX_URL
.equals(uri
)) {
191 rep
= root(session
, cookies
, login
);
192 } else if (WebLibraryUrls
.VERSION_URL
.equals(uri
)) {
193 rep
= newFixedLengthResponse(Status
.OK
,
195 Version
.getCurrentVersion().toString());
196 } else if (WebLibraryUrls
.isCoverUrl(uri
)) {
197 String luid
= params
.get("luid");
199 rep
= setCover(uri
, luid
, login
);
201 rep
= getCover(uri
, login
);
203 } else if (WebLibraryUrls
.isListUrl(uri
)) {
204 rep
= getList(uri
, login
);
205 } else if (WebLibraryUrls
.isStoryUrl(uri
)) {
206 String value
= params
.get("value");
208 rep
= setStoryPart(uri
, value
, login
);
210 rep
= getStoryPart(uri
, login
);
212 } else if (WebLibraryUrls
.isViewUrl(uri
)) {
213 rep
= getViewer(cookies
, uri
, login
);
214 } else if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
215 session
.getCookies().delete("cookie");
216 cookies
.remove("cookie");
217 rep
= loginPage(login(false, false), uri
);
218 } else if (WebLibraryUrls
.isImprtUrl(uri
)) {
219 String url
= params
.get("url");
221 rep
= imprt(uri
, url
, login
);
223 rep
= imprtProgress(uri
, login
);
225 } else if (WebLibraryUrls
.isDeleteUrl(uri
)) {
226 rep
= delete(uri
, login
);
227 } else if (WebLibraryUrls
.EXIT_URL
.equals(uri
)) {
228 rep
= WebLibraryServerHtml
.this.stop(login
);
230 getTraceHandler().error(
231 "Supported URL was not processed: "
233 rep
= newFixedLengthResponse(
234 Status
.INTERNAL_ERROR
,
235 NanoHTTPD
.MIME_PLAINTEXT
,
236 "An error happened");
239 if (uri
.startsWith("/"))
240 uri
= uri
.substring(1);
241 InputStream in
= IOUtils
.openResource(
242 WebLibraryServerIndex
.class, uri
);
244 String mimeType
= MIME_PLAINTEXT
;
245 if (uri
.endsWith(".css")) {
246 mimeType
= "text/css";
247 } else if (uri
.endsWith(".html")) {
248 mimeType
= "text/html";
249 } else if (uri
.endsWith(".js")) {
250 mimeType
= "text/javascript";
252 rep
= newChunkedResponse(Status
.OK
, mimeType
,
257 getTraceHandler().trace("404: " + uri
);
258 rep
= newFixedLengthResponse(Status
.NOT_FOUND
,
259 NanoHTTPD
.MIME_PLAINTEXT
, "Not Found");
263 } catch (Exception e
) {
264 Instance
.getInstance().getTraceHandler().error(
265 new IOException("Cannot process web request", e
));
266 rep
= newFixedLengthResponse(Status
.INTERNAL_ERROR
,
267 NanoHTTPD
.MIME_PLAINTEXT
, "An error occured");
275 getTraceHandler().trace("Install SSL on the web server...");
276 server
.makeSecure(ssf
, null);
277 getTraceHandler().trace("Done.");
284 server
.start(NanoHTTPD
.SOCKET_READ_TIMEOUT
, false);
285 } catch (IOException e
) {
286 tracer
.error(new IOException("Cannot start the web server", e
));
290 protected void doStop() {
295 * The traces handler for this {@link WebLibraryServerHtml}.
297 * @return the traces handler
299 public TraceHandler
getTraceHandler() {
304 * The traces handler for this {@link WebLibraryServerHtml}.
307 * the new traces handler
309 public void setTraceHandler(TraceHandler tracer
) {
310 if (tracer
== null) {
311 tracer
= new TraceHandler(false, false, false);
314 this.tracer
= tracer
;
317 private Response
loginPage(WLoginResult login
, String uri
)
319 StringBuilder builder
= new StringBuilder();
321 builder
.append(getTemplateIndexPreBanner(true));
323 if (login
.isBadLogin()) {
325 "\t\t<div class='error'>Bad login or password</div>");
326 } else if (login
.isBadCookie()) {
328 "\t\t<div class='error'>Your session timed out</div>");
331 if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
332 uri
= WebLibraryUrls
.INDEX_URL
;
335 builder
.append("\t\t<form method='POST' action='" + uri
336 + "' class='login'>\n");
338 "\t\t\t<p>You must be logged into the system to see the stories.</p>");
339 builder
.append("\t\t\t<input type='text' name='login' />\n");
340 builder
.append("\t\t\t<input type='password' name='password' />\n");
341 builder
.append("\t\t\t<input type='submit' value='Login' />\n");
342 builder
.append("\t\t</form>\n");
344 builder
.append(getTemplate("index.post"));
346 return NanoHTTPD
.newFixedLengthResponse(Status
.FORBIDDEN
,
347 NanoHTTPD
.MIME_HTML
, builder
.toString());
350 private Response
root(IHTTPSession session
, Map
<String
, String
> cookies
,
351 WLoginResult login
) throws IOException
{
352 BasicLibrary lib
= Instance
.getInstance().getLibrary();
353 MetaResultList result
= new MetaResultList(metas(login
));
355 Map
<String
, String
> params
= session
.getParms();
357 String filter
= cookies
.get("filter");
358 if (params
.get("optionNo") != null)
360 if (filter
== null) {
364 String browser
= params
.get("browser") == null ?
""
365 : params
.get("browser");
366 String browser2
= params
.get("browser2") == null ?
""
367 : params
.get("browser2");
368 String browser3
= params
.get("browser3") == null ?
""
369 : params
.get("browser3");
371 String filterSource
= null;
372 String filterAuthor
= null;
373 String filterTag
= null;
375 // TODO: javascript in realtime, using visible=false + hide [submit]
377 List
<Template
> selects
= new ArrayList
<Template
>();
378 boolean sourcesSel
= false;
379 boolean authorsSel
= false;
380 boolean tagsSel
= false;
382 if (!browser
.isEmpty()) {
383 List
<Template
> options
= new ArrayList
<Template
>();
385 if (browser
.equals("sources")) {
387 filterSource
= browser2
.isEmpty() ? filterSource
: browser2
;
389 // TODO: if 1 group -> no group
390 Map
<String
, List
<String
>> sources
= result
.getSourcesGrouped();
391 for (String source
: sources
.keySet()) {
393 templates
.browserOption(source
, source
, browser2
));
395 } else if (browser
.equals("authors")) {
397 filterAuthor
= browser2
.isEmpty() ? filterAuthor
: browser2
;
399 // TODO: if 1 group -> no group
400 Map
<String
, List
<String
>> authors
= result
.getAuthorsGrouped();
401 for (String author
: authors
.keySet()) {
403 templates
.browserOption(author
, author
, browser2
));
405 } else if (browser
.equals("tags")) {
407 filterTag
= browser2
.isEmpty() ? filterTag
: browser2
;
409 for (String tag
: result
.getTags()) {
410 options
.add(templates
.browserOption(tag
, tag
, browser2
));
414 selects
.add(templates
.browserSelect("browser2", browser2
, options
));
417 if (!browser2
.isEmpty()) {
418 List
<Template
> options
= new ArrayList
<Template
>();
420 if (browser
.equals("sources")) {
421 filterSource
= browser3
.isEmpty() ? filterSource
: browser3
;
422 Map
<String
, List
<String
>> sourcesGrouped
= result
423 .getSourcesGrouped();
424 List
<String
> sources
= sourcesGrouped
.get(browser2
);
425 if (sources
!= null && !sources
.isEmpty()) {
426 // TODO: single empty value
427 for (String source
: sources
) {
428 options
.add(templates
.browserOption(source
, source
,
432 } else if (browser
.equals("authors")) {
433 filterAuthor
= browser3
.isEmpty() ? filterAuthor
: browser3
;
434 Map
<String
, List
<String
>> authorsGrouped
= result
435 .getAuthorsGrouped();
436 List
<String
> authors
= authorsGrouped
.get(browser2
);
437 if (authors
!= null && !authors
.isEmpty()) {
438 // TODO: single empty value
439 for (String author
: authors
) {
440 options
.add(templates
.browserOption(author
, author
,
446 selects
.add(templates
.browserSelect("browser3", browser3
, options
));
449 List
<Template
> booklines
= new ArrayList
<Template
>();
450 for (MetaData meta
: result
.getMetas()) {
451 if (!filter
.isEmpty() && !meta
.getTitle().toLowerCase()
452 .contains(filter
.toLowerCase())) {
457 if (filterSource
!= null
458 && !filterSource
.equals(meta
.getSource())) {
463 if (filterAuthor
!= null
464 && !filterAuthor
.equals(meta
.getAuthor())) {
468 if (filterTag
!= null && !meta
.getTags().contains(filterTag
)) {
473 if (meta
.getAuthor() != null && !meta
.getAuthor().isEmpty()) {
474 author
= "(" + meta
.getAuthor() + ")";
477 booklines
.add(templates
.bookline( //
479 WebLibraryUrls
.getViewUrl(meta
.getLuid(), null, null), //
482 lib
.isCached(meta
.getLuid()) //
486 // Add the browser in front of the booklines
487 booklines
.add(0, templates
.browser(browser
, filter
, selects
));
489 return newInputStreamResponse(NanoHTTPD
.MIME_HTML
,
490 templates
.index(true, booklines
).read());
493 private Response
getViewer(Map
<String
, String
> cookies
, String uri
,
494 WLoginResult login
) {
495 String
[] cover
= uri
.split("/");
498 if (cover
.length
< off
+ 2) {
499 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
500 NanoHTTPD
.MIME_PLAINTEXT
, null);
503 String type
= cover
[off
+ 0];
504 String luid
= cover
[off
+ 1];
505 String chapterStr
= cover
.length
< off
+ 3 ?
null : cover
[off
+ 2];
506 String paragraphStr
= cover
.length
< off
+ 4 ?
null : cover
[off
+ 3];
508 // 1-based (0 = desc)
510 if (chapterStr
!= null) {
512 chapter
= Integer
.parseInt(chapterStr
);
514 throw new NumberFormatException();
516 } catch (NumberFormatException e
) {
517 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
518 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter is not valid");
524 if (paragraphStr
!= null) {
526 paragraph
= Integer
.parseInt(paragraphStr
);
527 if (paragraph
<= 0) {
528 throw new NumberFormatException();
530 } catch (NumberFormatException e
) {
531 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
532 NanoHTTPD
.MIME_PLAINTEXT
, "Paragraph is not valid");
537 Story story
= story(luid
, login
);
539 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
540 NanoHTTPD
.MIME_PLAINTEXT
, "Story not found");
543 // For images documents, always go to the images if not chap 0 desc
544 if (story
.getMeta().isImageDocument()) {
545 if (chapter
> 0 && paragraph
<= 0)
551 chap
= story
.getMeta().getResume();
554 chap
= story
.getChapters().get(chapter
- 1);
555 } catch (IndexOutOfBoundsException e
) {
556 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
557 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter not found");
561 String first
, previous
, next
, last
;
565 String disabledLeft
= "";
566 String disabledRight
= "";
567 String disabledZoomReal
= "";
568 String disabledZoomWidth
= "";
569 String disabledZoomHeight
= "";
571 if (paragraph
<= 0) {
572 first
= WebLibraryUrls
.getViewUrl(luid
, 0, null);
573 previous
= WebLibraryUrls
.getViewUrl(luid
,
574 (Math
.max(chapter
- 1, 0)), null);
575 next
= WebLibraryUrls
.getViewUrl(luid
,
576 (Math
.min(chapter
+ 1, story
.getChapters().size())),
578 last
= WebLibraryUrls
.getViewUrl(luid
,
579 story
.getChapters().size(), null);
583 StringBuilder desclines
= new StringBuilder();
584 Map
<String
, String
> details
= BasicLibrary
585 .getMetaDesc(story
.getMeta());
586 for (String key
: details
.keySet()) {
587 desclines
.append(getTemplate("viewer.descline") //
588 .replace("${key}", key
) //
589 .replace("${value}", details
.get(key
)) //
593 desc
= getTemplate("viewer.desc") //
594 .replace("${title}", story
.getMeta().getTitle()) //
595 .replace("${href}", next
) //
597 WebLibraryUrls
.getStoryUrlCover(luid
)) //
598 .replace("${details}", desclines
.toString()) //
602 viewer
= getTemplate("viewer.text") //
603 .replace("${desc}", desc
) //
605 if (chap
.getParagraphs().size() <= 0) {
606 viewer
= viewer
.replace("${content}",
607 "No content provided.");
609 viewer
= viewer
.replace("${content}",
610 new TextOutput(false).convert(chap
, chapter
> 0));
614 disabledLeft
= " disabled='disbaled'";
615 if (chapter
>= story
.getChapters().size())
616 disabledRight
= " disabled='disbaled'";
618 first
= WebLibraryUrls
.getViewUrl(luid
, chapter
, 1);
619 previous
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
620 (Math
.max(paragraph
- 1, 1)));
621 next
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
622 (Math
.min(paragraph
+ 1, chap
.getParagraphs().size())));
623 last
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
624 chap
.getParagraphs().size());
627 disabledLeft
= " disabled='disbaled'";
628 if (paragraph
>= chap
.getParagraphs().size())
629 disabledRight
= " disabled='disbaled'";
631 // First -> previous *chapter*
634 first
= WebLibraryUrls
.getViewUrl(luid
,
635 (Math
.max(chapter
- 1, 0)), null);
636 if (paragraph
<= 1) {
640 Paragraph para
= null;
642 para
= chap
.getParagraphs().get(paragraph
- 1);
643 } catch (IndexOutOfBoundsException e
) {
644 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
645 NanoHTTPD
.MIME_PLAINTEXT
,
646 "Paragraph " + paragraph
+ " not found");
649 if (para
.getType() == ParagraphType
.IMAGE
) {
650 String zoomStyle
= "max-width: 100%;";
651 disabledZoomWidth
= " disabled='disabled'";
652 String zoomOption
= cookies
.get("zoom");
653 if (zoomOption
!= null && !zoomOption
.isEmpty()) {
654 if (zoomOption
.equals("real")) {
656 disabledZoomWidth
= "";
657 disabledZoomReal
= " disabled='disabled'";
658 } else if (zoomOption
.equals("width")) {
659 zoomStyle
= "max-width: 100%;";
660 } else if (zoomOption
.equals("height")) {
661 // see height of navbar + optionbar
662 zoomStyle
= "max-height: calc(100% - 128px);";
663 disabledZoomWidth
= "";
664 disabledZoomHeight
= " disabled='disabled'";
668 viewer
= getTemplate("viewer.image") //
669 .replace("${href}", next
) //
670 .replace("${zoomStyle}", zoomStyle
) //
671 .replace("${src}", WebLibraryUrls
.getStoryUrl(luid
,
672 chapter
, paragraph
)) //
675 viewer
= getTemplate("viewer.text") //
676 .replace("${desc}", "") //
677 .replace("${content}",
678 new TextOutput(false).convert(para
)) //
683 // List of chap/para links
684 StringBuilder links
= new StringBuilder();
685 links
.append(getTemplate("viewer.link") //
687 WebLibraryUrls
.getViewUrl(luid
, 0, null)) //
689 paragraph
== 0 && chapter
== 0 ?
"selected" : "") //
690 .replace("${name}", "Description") //
693 for (int i
= 1; i
<= chap
.getParagraphs().size(); i
++) {
694 links
.append(getTemplate("viewer.link") //
696 WebLibraryUrls
.getViewUrl(luid
, chapter
, i
)) //
698 paragraph
== i ?
"selected" : "") //
699 .replace("${name}", "Image " + i
) //
704 for (Chapter c
: story
.getChapters()) {
705 String chapName
= "Chapter " + c
.getNumber();
706 if (c
.getName() != null && !c
.getName().isEmpty()) {
707 chapName
+= ": " + c
.getName();
710 links
.append(getTemplate("viewer.link") //
712 WebLibraryUrls
.getViewUrl(luid
, i
, null)) //
713 .replace("${class}", chapter
== i ?
"selected" : "") //
714 .replace("${name}", chapName
) //
721 // Buttons on the optionbar
723 StringBuilder buttons
= new StringBuilder();
724 buttons
.append(getTemplate("viewer.optionbar.button") //
725 .replace("${disabled}", "") //
726 .replace("${class}", "back") //
727 .replace("${href}", "/") //
728 .replace("${value}", "Back") //
731 buttons
.append(getTemplate("viewer.optionbar.button") //
732 .replace("${disabled}", disabledZoomReal
) //
733 .replace("${class}", "zoomreal") //
735 uri
+ "?optionName=zoom&optionValue=real") //
736 .replace("${value}", "1:1") //
738 buttons
.append(getTemplate("viewer.optionbar.button") //
739 .replace("${disabled}", disabledZoomWidth
) //
740 .replace("${class}", "zoomwidth") //
742 uri
+ "?optionName=zoom&optionValue=width") //
743 .replace("${value}", "Width") //
745 buttons
.append(getTemplate("viewer.optionbar.button") //
746 .replace("${disabled}", disabledZoomHeight
) //
747 .replace("${class}", "zoomheight") //
749 uri
+ "?optionName=zoom&optionValue=height") //
750 .replace("${value}", "Height") //
756 StringBuilder builder
= new StringBuilder();
758 builder
.append(getTemplateIndexPreBanner(false));
760 builder
.append(getTemplate("viewer.navbar") //
761 .replace("${disabledFirst}", disabledLeft
) //
762 .replace("${disabledPrevious}", disabledLeft
) //
763 .replace("${disabledNext}", disabledRight
) //
764 .replace("${disabledLast}", disabledRight
) //
765 .replace("${hrefFirst}", first
) //
766 .replace("${hrefPrevious}", previous
) //
767 .replace("${hrefNext}", next
) //
768 .replace("${hrefLast}", last
) //
769 .replace("${current}",
770 "" + (paragraph
> 0 ? paragraph
: chapter
)) //
771 .replace("${links}", links
.toString()) //
774 builder
.append(viewer
);
776 builder
.append(getTemplate("viewer.optionbar") //
777 .replace("${classSize}", (paragraph
> 0 ?
"s4" : "s1")) //
778 .replace("${buttons}", buttons
.toString()) //
781 builder
.append(getTemplate("index.post"));
783 return NanoHTTPD
.newFixedLengthResponse(Status
.OK
,
784 NanoHTTPD
.MIME_HTML
, builder
.toString());
785 } catch (IOException e
) {
786 Instance
.getInstance().getTraceHandler()
787 .error(new IOException("Cannot get image: " + uri
, e
));
788 return NanoHTTPD
.newFixedLengthResponse(Status
.INTERNAL_ERROR
,
789 NanoHTTPD
.MIME_PLAINTEXT
, "Error when processing request");
793 protected Response
newInputStreamResponse(String mimeType
, InputStream in
) {
795 return NanoHTTPD
.newFixedLengthResponse(Status
.NO_CONTENT
, "",
798 return NanoHTTPD
.newChunkedResponse(Status
.OK
, mimeType
, in
);
801 private String
getTemplateIndexPreBanner(boolean banner
)
803 String favicon
= "favicon.ico";
804 String icon
= Instance
.getInstance().getUiConfig()
805 .getString(UiConfig
.PROGRAM_ICON
);
807 favicon
= "icon_" + icon
.replace("-", "_") + ".png";
810 String html
= getTemplate("index.pre") //
811 .replace("${title}", "Fanfix") //
812 .replace("${favicon}", favicon
) //
816 html
+= getTemplate("index.banner") //
817 .replace("${favicon}", favicon
) //
818 .replace("${version}",
819 Version
.getCurrentVersion().toString()) //
826 private String
getTemplate(String template
) throws IOException
{
827 // TODO: check if it is "slow" -> map cache
828 InputStream in
= IOUtils
.openResource(WebLibraryServerTemplates
.class,
831 return IOUtils
.readSmallStream(in
);