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
.library
.web
.templates
.WebLibraryServerTemplates
;
26 import be
.nikiroo
.fanfix
.reader
.TextOutput
;
27 import be
.nikiroo
.utils
.IOUtils
;
28 import be
.nikiroo
.utils
.NanoHTTPD
;
29 import be
.nikiroo
.utils
.NanoHTTPD
.IHTTPSession
;
30 import be
.nikiroo
.utils
.NanoHTTPD
.Response
;
31 import be
.nikiroo
.utils
.NanoHTTPD
.Response
.Status
;
32 import be
.nikiroo
.utils
.TraceHandler
;
33 import be
.nikiroo
.utils
.Version
;
35 abstract class WebLibraryServerHtml
implements Runnable
{
36 private NanoHTTPD server
;
37 protected TraceHandler tracer
= new TraceHandler();
39 abstract protected WLoginResult
login(String who
, String cookie
);
41 abstract protected WLoginResult
login(String who
, String key
,
44 abstract protected WLoginResult
login(boolean badLogin
, boolean badCookie
);
46 abstract protected Response
getList(String uri
, WLoginResult login
)
49 abstract protected Response
getStoryPart(String uri
, WLoginResult login
);
51 abstract protected Response
setStoryPart(String uri
, String value
,
52 WLoginResult login
) throws IOException
;
54 abstract protected Response
getCover(String uri
, WLoginResult login
)
57 abstract protected Response
setCover(String uri
, String luid
,
58 WLoginResult login
) throws IOException
;
60 abstract protected List
<MetaData
> metas(WLoginResult login
)
63 abstract protected Story
story(String luid
, WLoginResult login
)
66 protected abstract Response
imprt(String uri
, String url
,
67 WLoginResult login
) throws IOException
;
69 protected abstract Response
imprtProgress(String uri
, WLoginResult login
);
71 protected abstract Response
delete(String uri
, WLoginResult login
)
75 * Wait until all operations are done and stop the server.
77 * All the new R/W operations will be refused after a call to stop.
79 protected abstract Response
stop(WLoginResult login
);
81 public WebLibraryServerHtml(boolean secure
) throws IOException
{
82 Integer port
= Instance
.getInstance().getConfig()
83 .getInteger(Config
.SERVER_PORT
);
85 throw new IOException(
86 "Cannot start web server: port not specified");
89 SSLServerSocketFactory ssf
= null;
91 String keystorePath
= Instance
.getInstance().getConfig()
92 .getString(Config
.SERVER_SSL_KEYSTORE
, "");
93 String keystorePass
= Instance
.getInstance().getConfig()
94 .getString(Config
.SERVER_SSL_KEYSTORE_PASS
);
96 if (secure
&& keystorePath
.isEmpty()) {
97 throw new IOException(
98 "Cannot start a secure web server: no keystore.jks file povided");
101 if (!keystorePath
.isEmpty()) {
102 File keystoreFile
= new File(keystorePath
);
104 KeyStore keystore
= KeyStore
105 .getInstance(KeyStore
.getDefaultType());
106 InputStream keystoreStream
= new FileInputStream(
109 keystore
.load(keystoreStream
,
110 keystorePass
.toCharArray());
111 KeyManagerFactory keyManagerFactory
= KeyManagerFactory
112 .getInstance(KeyManagerFactory
113 .getDefaultAlgorithm());
114 keyManagerFactory
.init(keystore
,
115 keystorePass
.toCharArray());
116 ssf
= NanoHTTPD
.makeSSLSocketFactory(keystore
,
119 keystoreStream
.close();
121 } catch (Exception e
) {
122 throw new IOException(e
.getMessage());
127 server
= new NanoHTTPD(port
) {
129 public Response
serve(final IHTTPSession session
) {
130 super.serve(session
);
132 String query
= session
.getQueryParameterString(); // a=a%20b&dd=2
133 Method method
= session
.getMethod(); // GET, POST..
134 String uri
= session
.getUri(); // /home.html
136 // need them in real time (not just those sent by the UA)
137 Map
<String
, String
> cookies
= new HashMap
<String
, String
>();
138 for (String cookie
: session
.getCookies()) {
139 cookies
.put(cookie
, session
.getCookies().read(cookie
));
142 WLoginResult login
= null;
143 Map
<String
, String
> params
= session
.getParms();
144 String who
= session
.getRemoteHostName()
145 + session
.getRemoteIpAddress();
146 if (params
.get("login") != null) {
147 login
= login(who
, params
.get("password"),
148 params
.get("login"));
150 String cookie
= cookies
.get("cookie");
151 login
= login(who
, cookie
);
154 if (login
.isSuccess()) {
156 session
.getCookies().set(new Cookie("cookie",
157 login
.getCookie(), "30; path=/"));
160 String optionName
= params
.get("optionName");
161 if (optionName
!= null && !optionName
.isEmpty()) {
162 String optionNo
= params
.get("optionNo");
163 String optionValue
= params
.get("optionValue");
164 if (optionNo
!= null || optionValue
== null
165 || optionValue
.isEmpty()) {
166 session
.getCookies().delete(optionName
);
167 cookies
.remove(optionName
);
169 session
.getCookies().set(new Cookie(optionName
,
170 optionValue
, "; path=/"));
171 cookies
.put(optionName
, optionValue
);
178 if (!login
.isSuccess()
179 && WebLibraryUrls
.isSupportedUrl(uri
, true)) {
180 rep
= loginPage(login
, uri
);
184 if (WebLibraryUrls
.isSupportedUrl(uri
, false)) {
185 if (WebLibraryUrls
.INDEX_URL
.equals(uri
)) {
186 rep
= root(session
, cookies
, login
);
187 } else if (WebLibraryUrls
.VERSION_URL
.equals(uri
)) {
188 rep
= newFixedLengthResponse(Status
.OK
,
190 Version
.getCurrentVersion().toString());
191 } else if (WebLibraryUrls
.isCoverUrl(uri
)) {
192 String luid
= params
.get("luid");
194 rep
= setCover(uri
, luid
, login
);
196 rep
= getCover(uri
, login
);
198 } else if (WebLibraryUrls
.isListUrl(uri
)) {
199 rep
= getList(uri
, login
);
200 } else if (WebLibraryUrls
.isStoryUrl(uri
)) {
201 String value
= params
.get("value");
203 rep
= setStoryPart(uri
, value
, login
);
205 rep
= getStoryPart(uri
, login
);
207 } else if (WebLibraryUrls
.isViewUrl(uri
)) {
208 rep
= getViewer(cookies
, uri
, login
);
209 } else if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
210 session
.getCookies().delete("cookie");
211 cookies
.remove("cookie");
212 rep
= loginPage(login(false, false), uri
);
213 } else if (WebLibraryUrls
.isImprtUrl(uri
)) {
214 String url
= params
.get("url");
216 rep
= imprt(uri
, url
, login
);
218 rep
= imprtProgress(uri
, login
);
220 } else if (WebLibraryUrls
.isDeleteUrl(uri
)) {
221 rep
= delete(uri
, login
);
222 } else if (WebLibraryUrls
.EXIT_URL
.equals(uri
)) {
223 rep
= WebLibraryServerHtml
.this.stop(login
);
225 getTraceHandler().error(
226 "Supported URL was not processed: "
228 rep
= newFixedLengthResponse(
229 Status
.INTERNAL_ERROR
,
230 NanoHTTPD
.MIME_PLAINTEXT
,
231 "An error happened");
234 if (uri
.startsWith("/"))
235 uri
= uri
.substring(1);
236 InputStream in
= IOUtils
.openResource(
237 WebLibraryServerIndex
.class, uri
);
239 String mimeType
= MIME_PLAINTEXT
;
240 if (uri
.endsWith(".css")) {
241 mimeType
= "text/css";
242 } else if (uri
.endsWith(".html")) {
243 mimeType
= "text/html";
244 } else if (uri
.endsWith(".js")) {
245 mimeType
= "text/javascript";
247 rep
= newChunkedResponse(Status
.OK
, mimeType
,
252 getTraceHandler().trace("404: " + uri
);
253 rep
= newFixedLengthResponse(Status
.NOT_FOUND
,
254 NanoHTTPD
.MIME_PLAINTEXT
, "Not Found");
258 } catch (Exception e
) {
259 Instance
.getInstance().getTraceHandler().error(
260 new IOException("Cannot process web request", e
));
261 rep
= newFixedLengthResponse(Status
.INTERNAL_ERROR
,
262 NanoHTTPD
.MIME_PLAINTEXT
, "An error occured");
270 getTraceHandler().trace("Install SSL on the web server...");
271 server
.makeSecure(ssf
, null);
272 getTraceHandler().trace("Done.");
279 server
.start(NanoHTTPD
.SOCKET_READ_TIMEOUT
, false);
280 } catch (IOException e
) {
281 tracer
.error(new IOException("Cannot start the web server", e
));
285 protected void doStop() {
290 * The traces handler for this {@link WebLibraryServerHtml}.
292 * @return the traces handler
294 public TraceHandler
getTraceHandler() {
299 * The traces handler for this {@link WebLibraryServerHtml}.
302 * the new traces handler
304 public void setTraceHandler(TraceHandler tracer
) {
305 if (tracer
== null) {
306 tracer
= new TraceHandler(false, false, false);
309 this.tracer
= tracer
;
312 private Response
loginPage(WLoginResult login
, String uri
)
314 StringBuilder builder
= new StringBuilder();
316 builder
.append(getTemplateIndexPreBanner(true));
318 if (login
.isBadLogin()) {
320 "\t\t<div class='error'>Bad login or password</div>");
321 } else if (login
.isBadCookie()) {
323 "\t\t<div class='error'>Your session timed out</div>");
326 if (WebLibraryUrls
.LOGOUT_URL
.equals(uri
)) {
327 uri
= WebLibraryUrls
.INDEX_URL
;
330 builder
.append("\t\t<form method='POST' action='" + uri
331 + "' class='login'>\n");
333 "\t\t\t<p>You must be logged into the system to see the stories.</p>");
334 builder
.append("\t\t\t<input type='text' name='login' />\n");
335 builder
.append("\t\t\t<input type='password' name='password' />\n");
336 builder
.append("\t\t\t<input type='submit' value='Login' />\n");
337 builder
.append("\t\t</form>\n");
339 builder
.append(getTemplate("index.post"));
341 return NanoHTTPD
.newFixedLengthResponse(Status
.FORBIDDEN
,
342 NanoHTTPD
.MIME_HTML
, builder
.toString());
345 private Response
root(IHTTPSession session
, Map
<String
, String
> cookies
,
346 WLoginResult login
) throws IOException
{
347 BasicLibrary lib
= Instance
.getInstance().getLibrary();
348 MetaResultList result
= new MetaResultList(metas(login
));
349 StringBuilder builder
= new StringBuilder();
351 builder
.append(getTemplateIndexPreBanner(true));
353 Map
<String
, String
> params
= session
.getParms();
355 String filter
= cookies
.get("filter");
356 if (params
.get("optionNo") != null)
358 if (filter
== null) {
362 String browser
= params
.get("browser") == null ?
""
363 : params
.get("browser");
364 String browser2
= params
.get("browser2") == null ?
""
365 : params
.get("browser2");
366 String browser3
= params
.get("browser3") == null ?
""
367 : params
.get("browser3");
369 String filterSource
= null;
370 String filterAuthor
= null;
371 String filterTag
= null;
373 // TODO: javascript in realtime, using visible=false + hide [submit]
375 StringBuilder selects
= new StringBuilder();
376 boolean sourcesSel
= false;
377 boolean authorsSel
= false;
378 boolean tagsSel
= false;
380 String selectTemplate
= getTemplate("browser.select");
382 if (!browser
.isEmpty()) {
383 StringBuilder options
= new StringBuilder();
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 getTemplateBrowserOption(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 getTemplateBrowserOption(author
, author
, browser2
));
405 } else if (browser
.equals("tags")) {
407 filterTag
= browser2
.isEmpty() ? filterTag
: browser2
;
409 for (String tag
: result
.getTags()) {
411 getTemplateBrowserOption(tag
, tag
, browser2
));
415 selects
.append(selectTemplate
//
416 .replace("${name}", "browser2") //
417 .replace("${value}", browser2
) //
418 .replace("${options}", options
.toString()) //
422 if (!browser2
.isEmpty()) {
423 StringBuilder options
= new StringBuilder();
425 if (browser
.equals("sources")) {
426 filterSource
= browser3
.isEmpty() ? filterSource
: browser3
;
427 Map
<String
, List
<String
>> sourcesGrouped
= result
428 .getSourcesGrouped();
429 List
<String
> sources
= sourcesGrouped
.get(browser2
);
430 if (sources
!= null && !sources
.isEmpty()) {
431 // TODO: single empty value
432 for (String source
: sources
) {
433 options
.append(getTemplateBrowserOption(source
, source
,
437 } else if (browser
.equals("authors")) {
438 filterAuthor
= browser3
.isEmpty() ? filterAuthor
: browser3
;
439 Map
<String
, List
<String
>> authorsGrouped
= result
440 .getAuthorsGrouped();
441 List
<String
> authors
= authorsGrouped
.get(browser2
);
442 if (authors
!= null && !authors
.isEmpty()) {
443 // TODO: single empty value
444 for (String author
: authors
) {
445 options
.append(getTemplateBrowserOption(author
, author
,
451 selects
.append(selectTemplate
//
452 .replace("${name}", "browser3") //
453 .replace("${value}", browser3
) //
454 .replace("${options}", options
.toString()) //
458 String sel
= "selected='selected'";
459 builder
.append(getTemplate("browser") //
460 .replace("${sourcesSelected}", sourcesSel ? sel
: "") //
461 .replace("${authorsSelected}", authorsSel ? sel
: "") //
462 .replace("${tagsSelected}", tagsSel ? sel
: "") //
463 .replace("${filter}", filter
) //
464 .replace("${selects}", selects
.toString()) //
467 builder
.append("\t\t<div class='books'>\n");
468 for (MetaData meta
: result
.getMetas()) {
469 if (!filter
.isEmpty() && !meta
.getTitle().toLowerCase()
470 .contains(filter
.toLowerCase())) {
475 if (filterSource
!= null
476 && !filterSource
.equals(meta
.getSource())) {
481 if (filterAuthor
!= null
482 && !filterAuthor
.equals(meta
.getAuthor())) {
486 if (filterTag
!= null && !meta
.getTags().contains(filterTag
)) {
491 if (meta
.getAuthor() != null && !meta
.getAuthor().isEmpty()) {
492 author
= "(" + meta
.getAuthor() + ")";
495 String cachedClass
= "cached";
496 String cached
= "◉";
497 if (!lib
.isCached(meta
.getLuid())) {
498 cachedClass
= "uncached";
502 builder
.append(getTemplate("bookline") //
504 WebLibraryUrls
.getViewUrl(meta
.getLuid(), null,
506 .replace("${luid}", meta
.getLuid()) //
507 .replace("${title}", meta
.getTitle()) //
508 .replace("${author}", author
) //
509 .replace("${cachedClass}", cachedClass
) //
510 .replace("${cached}", cached
) //
513 builder
.append("\t\t</div>\n");
515 builder
.append(getTemplate("index.post"));
517 return NanoHTTPD
.newFixedLengthResponse(builder
.toString());
520 private Response
getViewer(Map
<String
, String
> cookies
, String uri
,
521 WLoginResult login
) {
522 String
[] cover
= uri
.split("/");
525 if (cover
.length
< off
+ 2) {
526 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
527 NanoHTTPD
.MIME_PLAINTEXT
, null);
530 String type
= cover
[off
+ 0];
531 String luid
= cover
[off
+ 1];
532 String chapterStr
= cover
.length
< off
+ 3 ?
null : cover
[off
+ 2];
533 String paragraphStr
= cover
.length
< off
+ 4 ?
null : cover
[off
+ 3];
535 // 1-based (0 = desc)
537 if (chapterStr
!= null) {
539 chapter
= Integer
.parseInt(chapterStr
);
541 throw new NumberFormatException();
543 } catch (NumberFormatException e
) {
544 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
545 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter is not valid");
551 if (paragraphStr
!= null) {
553 paragraph
= Integer
.parseInt(paragraphStr
);
554 if (paragraph
<= 0) {
555 throw new NumberFormatException();
557 } catch (NumberFormatException e
) {
558 return NanoHTTPD
.newFixedLengthResponse(Status
.BAD_REQUEST
,
559 NanoHTTPD
.MIME_PLAINTEXT
, "Paragraph is not valid");
564 Story story
= story(luid
, login
);
566 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
567 NanoHTTPD
.MIME_PLAINTEXT
, "Story not found");
570 // For images documents, always go to the images if not chap 0 desc
571 if (story
.getMeta().isImageDocument()) {
572 if (chapter
> 0 && paragraph
<= 0)
578 chap
= story
.getMeta().getResume();
581 chap
= story
.getChapters().get(chapter
- 1);
582 } catch (IndexOutOfBoundsException e
) {
583 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
584 NanoHTTPD
.MIME_PLAINTEXT
, "Chapter not found");
588 String first
, previous
, next
, last
;
592 String disabledLeft
= "";
593 String disabledRight
= "";
594 String disabledZoomReal
= "";
595 String disabledZoomWidth
= "";
596 String disabledZoomHeight
= "";
598 if (paragraph
<= 0) {
599 first
= WebLibraryUrls
.getViewUrl(luid
, 0, null);
600 previous
= WebLibraryUrls
.getViewUrl(luid
,
601 (Math
.max(chapter
- 1, 0)), null);
602 next
= WebLibraryUrls
.getViewUrl(luid
,
603 (Math
.min(chapter
+ 1, story
.getChapters().size())),
605 last
= WebLibraryUrls
.getViewUrl(luid
,
606 story
.getChapters().size(), null);
610 StringBuilder desclines
= new StringBuilder();
611 Map
<String
, String
> details
= BasicLibrary
612 .getMetaDesc(story
.getMeta());
613 for (String key
: details
.keySet()) {
614 desclines
.append(getTemplate("viewer.descline") //
615 .replace("${key}", key
) //
616 .replace("${value}", details
.get(key
)) //
620 desc
= getTemplate("viewer.desc") //
621 .replace("${title}", story
.getMeta().getTitle()) //
622 .replace("${href}", next
) //
624 WebLibraryUrls
.getStoryUrlCover(luid
)) //
625 .replace("${details}", desclines
.toString()) //
629 viewer
= getTemplate("viewer.text") //
630 .replace("${desc}", desc
) //
632 if (chap
.getParagraphs().size() <= 0) {
633 viewer
= viewer
.replace("${content}",
634 "No content provided.");
636 viewer
= viewer
.replace("${content}",
637 new TextOutput(false).convert(chap
, chapter
> 0));
641 disabledLeft
= " disabled='disbaled'";
642 if (chapter
>= story
.getChapters().size())
643 disabledRight
= " disabled='disbaled'";
645 first
= WebLibraryUrls
.getViewUrl(luid
, chapter
, 1);
646 previous
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
647 (Math
.max(paragraph
- 1, 1)));
648 next
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
649 (Math
.min(paragraph
+ 1, chap
.getParagraphs().size())));
650 last
= WebLibraryUrls
.getViewUrl(luid
, chapter
,
651 chap
.getParagraphs().size());
654 disabledLeft
= " disabled='disbaled'";
655 if (paragraph
>= chap
.getParagraphs().size())
656 disabledRight
= " disabled='disbaled'";
658 // First -> previous *chapter*
661 first
= WebLibraryUrls
.getViewUrl(luid
,
662 (Math
.max(chapter
- 1, 0)), null);
663 if (paragraph
<= 1) {
667 Paragraph para
= null;
669 para
= chap
.getParagraphs().get(paragraph
- 1);
670 } catch (IndexOutOfBoundsException e
) {
671 return NanoHTTPD
.newFixedLengthResponse(Status
.NOT_FOUND
,
672 NanoHTTPD
.MIME_PLAINTEXT
,
673 "Paragraph " + paragraph
+ " not found");
676 if (para
.getType() == ParagraphType
.IMAGE
) {
677 String zoomStyle
= "max-width: 100%;";
678 disabledZoomWidth
= " disabled='disabled'";
679 String zoomOption
= cookies
.get("zoom");
680 if (zoomOption
!= null && !zoomOption
.isEmpty()) {
681 if (zoomOption
.equals("real")) {
683 disabledZoomWidth
= "";
684 disabledZoomReal
= " disabled='disabled'";
685 } else if (zoomOption
.equals("width")) {
686 zoomStyle
= "max-width: 100%;";
687 } else if (zoomOption
.equals("height")) {
688 // see height of navbar + optionbar
689 zoomStyle
= "max-height: calc(100% - 128px);";
690 disabledZoomWidth
= "";
691 disabledZoomHeight
= " disabled='disabled'";
695 viewer
= getTemplate("viewer.image") //
696 .replace("${href}", next
) //
697 .replace("${zoomStyle}", zoomStyle
) //
698 .replace("${src}", WebLibraryUrls
.getStoryUrl(luid
,
699 chapter
, paragraph
)) //
702 viewer
= getTemplate("viewer.text") //
703 .replace("${desc}", "") //
704 .replace("${content}",
705 new TextOutput(false).convert(para
)) //
710 // List of chap/para links
711 StringBuilder links
= new StringBuilder();
712 links
.append(getTemplate("viewer.link") //
714 WebLibraryUrls
.getViewUrl(luid
, 0, null)) //
716 paragraph
== 0 && chapter
== 0 ?
"selected" : "") //
717 .replace("${name}", "Description") //
720 for (int i
= 1; i
<= chap
.getParagraphs().size(); i
++) {
721 links
.append(getTemplate("viewer.link") //
723 WebLibraryUrls
.getViewUrl(luid
, chapter
, i
)) //
725 paragraph
== i ?
"selected" : "") //
726 .replace("${name}", "Image " + i
) //
731 for (Chapter c
: story
.getChapters()) {
732 String chapName
= "Chapter " + c
.getNumber();
733 if (c
.getName() != null && !c
.getName().isEmpty()) {
734 chapName
+= ": " + c
.getName();
737 links
.append(getTemplate("viewer.link") //
739 WebLibraryUrls
.getViewUrl(luid
, i
, null)) //
740 .replace("${class}", chapter
== i ?
"selected" : "") //
741 .replace("${name}", chapName
) //
748 // Buttons on the optionbar
750 StringBuilder buttons
= new StringBuilder();
751 buttons
.append(getTemplate("viewer.optionbar.button") //
752 .replace("${disabled}", "") //
753 .replace("${class}", "back") //
754 .replace("${href}", "/") //
755 .replace("${value}", "Back") //
758 buttons
.append(getTemplate("viewer.optionbar.button") //
759 .replace("${disabled}", disabledZoomReal
) //
760 .replace("${class}", "zoomreal") //
762 uri
+ "?optionName=zoom&optionValue=real") //
763 .replace("${value}", "1:1") //
765 buttons
.append(getTemplate("viewer.optionbar.button") //
766 .replace("${disabled}", disabledZoomWidth
) //
767 .replace("${class}", "zoomwidth") //
769 uri
+ "?optionName=zoom&optionValue=width") //
770 .replace("${value}", "Width") //
772 buttons
.append(getTemplate("viewer.optionbar.button") //
773 .replace("${disabled}", disabledZoomHeight
) //
774 .replace("${class}", "zoomheight") //
776 uri
+ "?optionName=zoom&optionValue=height") //
777 .replace("${value}", "Height") //
783 StringBuilder builder
= new StringBuilder();
785 builder
.append(getTemplateIndexPreBanner(false));
787 builder
.append(getTemplate("viewer.navbar") //
788 .replace("${disabledFirst}", disabledLeft
) //
789 .replace("${disabledPrevious}", disabledLeft
) //
790 .replace("${disabledNext}", disabledRight
) //
791 .replace("${disabledLast}", disabledRight
) //
792 .replace("${hrefFirst}", first
) //
793 .replace("${hrefPrevious}", previous
) //
794 .replace("${hrefNext}", next
) //
795 .replace("${hrefLast}", last
) //
796 .replace("${current}",
797 "" + (paragraph
> 0 ? paragraph
: chapter
)) //
798 .replace("${links}", links
.toString()) //
801 builder
.append(viewer
);
803 builder
.append(getTemplate("viewer.optionbar") //
804 .replace("${classSize}", (paragraph
> 0 ?
"s4" : "s1")) //
805 .replace("${buttons}", buttons
.toString()) //
808 builder
.append(getTemplate("index.post"));
810 return NanoHTTPD
.newFixedLengthResponse(Status
.OK
,
811 NanoHTTPD
.MIME_HTML
, builder
.toString());
812 } catch (IOException e
) {
813 Instance
.getInstance().getTraceHandler()
814 .error(new IOException("Cannot get image: " + uri
, e
));
815 return NanoHTTPD
.newFixedLengthResponse(Status
.INTERNAL_ERROR
,
816 NanoHTTPD
.MIME_PLAINTEXT
, "Error when processing request");
820 protected Response
newInputStreamResponse(String mimeType
, InputStream in
) {
822 return NanoHTTPD
.newFixedLengthResponse(Status
.NO_CONTENT
, "",
825 return NanoHTTPD
.newChunkedResponse(Status
.OK
, mimeType
, in
);
828 private String
getTemplateIndexPreBanner(boolean banner
)
830 String favicon
= "favicon.ico";
831 String icon
= Instance
.getInstance().getUiConfig()
832 .getString(UiConfig
.PROGRAM_ICON
);
834 favicon
= "icon_" + icon
.replace("-", "_") + ".png";
837 String html
= getTemplate("index.pre") //
838 .replace("${title}", "Fanfix") //
839 .replace("${favicon}", favicon
) //
843 html
+= getTemplate("index.banner") //
844 .replace("${favicon}", favicon
) //
845 .replace("${version}",
846 Version
.getCurrentVersion().toString()) //
853 private String
getTemplateBrowserOption(String name
, String value
,
854 String selected
) throws IOException
{
855 String selectedAttribute
= "";
856 if (value
.equals(selected
)) {
857 selectedAttribute
= " selected='selected'";
860 return getTemplate("browser.option" //
861 .replace("${value}", value
) //
862 .replace("${selected}", selectedAttribute
) //
863 .replace("${name}", name
) //
867 private String
getTemplate(String template
) throws IOException
{
868 // TODO: check if it is "slow" -> map cache
869 InputStream in
= IOUtils
.openResource(WebLibraryServerTemplates
.class,
872 return IOUtils
.readSmallStream(in
);