d5ba533f27b5ac746c631ccb2457ea3c3aa3649f
[fanfix.git] / src / be / nikiroo / fanfix / library / WebLibraryServerHtml.java
1 package be.nikiroo.fanfix.library;
2
3 import java.io.File;
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;
12 import java.util.Map;
13
14 import javax.net.ssl.KeyManagerFactory;
15 import javax.net.ssl.SSLServerSocketFactory;
16
17 import be.nikiroo.fanfix.Instance;
18 import be.nikiroo.fanfix.bundles.Config;
19 import be.nikiroo.fanfix.data.Chapter;
20 import be.nikiroo.fanfix.data.MetaData;
21 import be.nikiroo.fanfix.data.Paragraph;
22 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
23 import be.nikiroo.fanfix.data.Story;
24 import be.nikiroo.fanfix.library.WebLibraryServer.WLoginResult;
25 import be.nikiroo.fanfix.library.web.WebLibraryServerIndex;
26 import be.nikiroo.fanfix.library.web.templates.WebLibraryServerTemplates;
27 import be.nikiroo.fanfix.reader.TextOutput;
28 import be.nikiroo.utils.IOUtils;
29 import be.nikiroo.utils.NanoHTTPD;
30 import be.nikiroo.utils.NanoHTTPD.IHTTPSession;
31 import be.nikiroo.utils.NanoHTTPD.Response;
32 import be.nikiroo.utils.NanoHTTPD.Response.Status;
33 import be.nikiroo.utils.TraceHandler;
34 import be.nikiroo.utils.Version;
35
36 abstract class WebLibraryServerHtml implements Runnable {
37 private NanoHTTPD server;
38 protected TraceHandler tracer = new TraceHandler();
39
40 WebLibraryServerTemplates templates = WebLibraryServerTemplates
41 .getInstance();
42
43 abstract protected WLoginResult login(String who, String cookie);
44
45 abstract protected WLoginResult login(String who, String key,
46 String subkey);
47
48 abstract protected WLoginResult login(boolean badLogin, boolean badCookie);
49
50 abstract protected Response getList(String uri, WLoginResult login)
51 throws IOException;
52
53 abstract protected Response getStoryPart(String uri, WLoginResult login);
54
55 abstract protected Response setStoryPart(String uri, String value,
56 WLoginResult login) throws IOException;
57
58 abstract protected Response getCover(String uri, WLoginResult login)
59 throws IOException;
60
61 abstract protected Response setCover(String uri, String luid,
62 WLoginResult login) throws IOException;
63
64 abstract protected List<MetaData> metas(WLoginResult login)
65 throws IOException;
66
67 abstract protected Story story(String luid, WLoginResult login)
68 throws IOException;
69
70 protected abstract Response imprt(String uri, String url,
71 WLoginResult login) throws IOException;
72
73 protected abstract Response imprtProgress(String uri, WLoginResult login);
74
75 protected abstract Response delete(String uri, WLoginResult login)
76 throws IOException;
77
78 /**
79 * Wait until all operations are done and stop the server.
80 * <p>
81 * All the new R/W operations will be refused after a call to stop.
82 */
83 protected abstract Response stop(WLoginResult login);
84
85 public WebLibraryServerHtml(boolean secure) throws IOException {
86 Integer port = Instance.getInstance().getConfig()
87 .getInteger(Config.SERVER_PORT);
88 if (port == null) {
89 throw new IOException(
90 "Cannot start web server: port not specified");
91 }
92
93 SSLServerSocketFactory ssf = null;
94 if (secure) {
95 String keystorePath = Instance.getInstance().getConfig()
96 .getString(Config.SERVER_SSL_KEYSTORE, "");
97 String keystorePass = Instance.getInstance().getConfig()
98 .getString(Config.SERVER_SSL_KEYSTORE_PASS);
99
100 if (secure && keystorePath.isEmpty()) {
101 throw new IOException(
102 "Cannot start a secure web server: no keystore.jks file povided");
103 }
104
105 if (!keystorePath.isEmpty()) {
106 File keystoreFile = new File(keystorePath);
107 try {
108 KeyStore keystore = KeyStore
109 .getInstance(KeyStore.getDefaultType());
110 InputStream keystoreStream = new FileInputStream(
111 keystoreFile);
112 try {
113 keystore.load(keystoreStream,
114 keystorePass.toCharArray());
115 KeyManagerFactory keyManagerFactory = KeyManagerFactory
116 .getInstance(KeyManagerFactory
117 .getDefaultAlgorithm());
118 keyManagerFactory.init(keystore,
119 keystorePass.toCharArray());
120 ssf = NanoHTTPD.makeSSLSocketFactory(keystore,
121 keyManagerFactory);
122 } finally {
123 keystoreStream.close();
124 }
125 } catch (Exception e) {
126 throw new IOException(e.getMessage());
127 }
128 }
129 }
130
131 server = new NanoHTTPD(port) {
132 @Override
133 public Response serve(final IHTTPSession session) {
134 super.serve(session);
135
136 String query = session.getQueryParameterString(); // a=a%20b&dd=2
137 Method method = session.getMethod(); // GET, POST..
138 String uri = session.getUri(); // /home.html
139
140 // need them in real time (not just those sent by the UA)
141 Map<String, String> cookies = new HashMap<String, String>();
142 for (String cookie : session.getCookies()) {
143 cookies.put(cookie, session.getCookies().read(cookie));
144 }
145
146 WLoginResult login = null;
147 Map<String, String> params = session.getParms();
148 String who = session.getRemoteHostName()
149 + session.getRemoteIpAddress();
150 if (params.get("login") != null) {
151 login = login(who, params.get("password"),
152 params.get("login"));
153 } else {
154 String cookie = cookies.get("cookie");
155 login = login(who, cookie);
156 }
157
158 if (login.isSuccess()) {
159 // refresh cookie
160 session.getCookies().set(new Cookie("cookie",
161 login.getCookie(), "30; path=/"));
162
163 // set options
164 String optionName = params.get("optionName");
165 if (optionName != null && !optionName.isEmpty()) {
166 String optionNo = params.get("optionNo");
167 String optionValue = params.get("optionValue");
168 if (optionNo != null || optionValue == null
169 || optionValue.isEmpty()) {
170 session.getCookies().delete(optionName);
171 cookies.remove(optionName);
172 } else {
173 session.getCookies().set(new Cookie(optionName,
174 optionValue, "; path=/"));
175 cookies.put(optionName, optionValue);
176 }
177 }
178 }
179
180 Response rep = null;
181 try {
182 if (!login.isSuccess()
183 && WebLibraryUrls.isSupportedUrl(uri, true)) {
184 rep = loginPage(login, uri);
185 }
186
187 if (rep == null) {
188 if (WebLibraryUrls.isSupportedUrl(uri, false)) {
189 if (WebLibraryUrls.INDEX_URL.equals(uri)) {
190 rep = root(session, cookies, login);
191 } else if (WebLibraryUrls.VERSION_URL.equals(uri)) {
192 rep = newFixedLengthResponse(Status.OK,
193 MIME_PLAINTEXT,
194 Version.getCurrentVersion().toString());
195 } else if (WebLibraryUrls.isCoverUrl(uri)) {
196 String luid = params.get("luid");
197 if (luid != null) {
198 rep = setCover(uri, luid, login);
199 } else {
200 rep = getCover(uri, login);
201 }
202 } else if (WebLibraryUrls.isListUrl(uri)) {
203 rep = getList(uri, login);
204 } else if (WebLibraryUrls.isStoryUrl(uri)) {
205 String value = params.get("value");
206 if (value != null) {
207 rep = setStoryPart(uri, value, login);
208 } else {
209 rep = getStoryPart(uri, login);
210 }
211 } else if (WebLibraryUrls.isViewUrl(uri)) {
212 rep = getViewer(cookies, uri, login);
213 } else if (WebLibraryUrls.LOGOUT_URL.equals(uri)) {
214 session.getCookies().delete("cookie");
215 cookies.remove("cookie");
216 rep = loginPage(login(false, false), uri);
217 } else if (WebLibraryUrls.isImprtUrl(uri)) {
218 String url = params.get("url");
219 if (url != null) {
220 rep = imprt(uri, url, login);
221 } else {
222 rep = imprtProgress(uri, login);
223 }
224 } else if (WebLibraryUrls.isDeleteUrl(uri)) {
225 rep = delete(uri, login);
226 } else if (WebLibraryUrls.EXIT_URL.equals(uri)) {
227 rep = WebLibraryServerHtml.this.stop(login);
228 } else {
229 getTraceHandler().error(
230 "Supported URL was not processed: "
231 + uri);
232 rep = newFixedLengthResponse(
233 Status.INTERNAL_ERROR,
234 NanoHTTPD.MIME_PLAINTEXT,
235 "An error happened");
236 }
237 } else {
238 if (uri.startsWith("/"))
239 uri = uri.substring(1);
240 InputStream in = IOUtils.openResource(
241 WebLibraryServerIndex.class, uri);
242 if (in != null) {
243 String mimeType = MIME_PLAINTEXT;
244 if (uri.endsWith(".css")) {
245 mimeType = "text/css";
246 } else if (uri.endsWith(".html")) {
247 mimeType = "text/html";
248 } else if (uri.endsWith(".js")) {
249 mimeType = "text/javascript";
250 }
251 rep = newChunkedResponse(Status.OK, mimeType,
252 in);
253 }
254
255 if (rep == null) {
256 getTraceHandler().trace("404: " + uri);
257 rep = newFixedLengthResponse(Status.NOT_FOUND,
258 NanoHTTPD.MIME_PLAINTEXT, "Not Found");
259 }
260 }
261 }
262 } catch (Exception e) {
263 Instance.getInstance().getTraceHandler().error(
264 new IOException("Cannot process web request", e));
265 rep = newFixedLengthResponse(Status.INTERNAL_ERROR,
266 NanoHTTPD.MIME_PLAINTEXT, "An error occured");
267 }
268
269 return rep;
270 }
271 };
272
273 if (ssf != null) {
274 getTraceHandler().trace("Install SSL on the web server...");
275 server.makeSecure(ssf, null);
276 getTraceHandler().trace("Done.");
277 }
278 }
279
280 @Override
281 public void run() {
282 try {
283 server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
284 } catch (IOException e) {
285 tracer.error(new IOException("Cannot start the web server", e));
286 }
287 }
288
289 protected void doStop() {
290 server.stop();
291 }
292
293 /**
294 * The traces handler for this {@link WebLibraryServerHtml}.
295 *
296 * @return the traces handler
297 */
298 public TraceHandler getTraceHandler() {
299 return tracer;
300 }
301
302 /**
303 * The traces handler for this {@link WebLibraryServerHtml}.
304 *
305 * @param tracer
306 * the new traces handler
307 */
308 public void setTraceHandler(TraceHandler tracer) {
309 if (tracer == null) {
310 tracer = new TraceHandler(false, false, false);
311 }
312
313 this.tracer = tracer;
314 }
315
316 private Response loginPage(WLoginResult login, String uri)
317 throws IOException {
318 List<Template> content = new ArrayList<Template>();
319
320 if (login.isBadLogin()) {
321 content.add(templates.message("Bad login or password", true));
322 } else if (login.isBadCookie()) {
323 content.add(templates.message("Your session timed out", true));
324 }
325
326 content.add(templates.login(uri));
327
328 return NanoHTTPD.newChunkedResponse(Status.FORBIDDEN,
329 NanoHTTPD.MIME_HTML, templates.index(true, content).read());
330 }
331
332 private Response root(IHTTPSession session, Map<String, String> cookies,
333 WLoginResult login) throws IOException {
334 BasicLibrary lib = Instance.getInstance().getLibrary();
335 MetaResultList result = new MetaResultList(metas(login));
336
337 Map<String, String> params = session.getParms();
338
339 String filter = cookies.get("filter");
340 if (params.get("optionNo") != null)
341 filter = null;
342 if (filter == null) {
343 filter = "";
344 }
345
346 String browser = params.get("browser") == null ? ""
347 : params.get("browser");
348 String browser2 = params.get("browser2") == null ? ""
349 : params.get("browser2");
350 String browser3 = params.get("browser3") == null ? ""
351 : params.get("browser3");
352
353 String filterSource = null;
354 String filterAuthor = null;
355 String filterTag = null;
356
357 // TODO: javascript in realtime, using visible=false + hide [submit]
358
359 List<Template> selects = new ArrayList<Template>();
360 boolean sourcesSel = false;
361 boolean authorsSel = false;
362 boolean tagsSel = false;
363
364 if (!browser.isEmpty()) {
365 List<Template> options = new ArrayList<Template>();
366
367 if (browser.equals("sources")) {
368 sourcesSel = true;
369 filterSource = browser2.isEmpty() ? filterSource : browser2;
370
371 // TODO: if 1 group -> no group
372 Map<String, List<String>> sources = result.getSourcesGrouped();
373 for (String source : sources.keySet()) {
374 options.add(
375 templates.browserOption(source, source, browser2));
376 }
377 } else if (browser.equals("authors")) {
378 authorsSel = true;
379 filterAuthor = browser2.isEmpty() ? filterAuthor : browser2;
380
381 // TODO: if 1 group -> no group
382 Map<String, List<String>> authors = result.getAuthorsGrouped();
383 for (String author : authors.keySet()) {
384 options.add(
385 templates.browserOption(author, author, browser2));
386 }
387 } else if (browser.equals("tags")) {
388 tagsSel = true;
389 filterTag = browser2.isEmpty() ? filterTag : browser2;
390
391 for (String tag : result.getTags()) {
392 options.add(templates.browserOption(tag, tag, browser2));
393 }
394 }
395
396 selects.add(templates.browserSelect("browser2", browser2, options));
397 }
398
399 if (!browser2.isEmpty()) {
400 List<Template> options = new ArrayList<Template>();
401
402 if (browser.equals("sources")) {
403 filterSource = browser3.isEmpty() ? filterSource : browser3;
404 Map<String, List<String>> sourcesGrouped = result
405 .getSourcesGrouped();
406 List<String> sources = sourcesGrouped.get(browser2);
407 if (sources != null && !sources.isEmpty()) {
408 // TODO: single empty value
409 for (String source : sources) {
410 options.add(templates.browserOption(source, source,
411 browser3));
412 }
413 }
414 } else if (browser.equals("authors")) {
415 filterAuthor = browser3.isEmpty() ? filterAuthor : browser3;
416 Map<String, List<String>> authorsGrouped = result
417 .getAuthorsGrouped();
418 List<String> authors = authorsGrouped.get(browser2);
419 if (authors != null && !authors.isEmpty()) {
420 // TODO: single empty value
421 for (String author : authors) {
422 options.add(templates.browserOption(author, author,
423 browser3));
424 }
425 }
426 }
427
428 selects.add(templates.browserSelect("browser3", browser3, options));
429 }
430
431 List<Template> booklines = new ArrayList<Template>();
432 for (MetaData meta : result.getMetas()) {
433 if (!filter.isEmpty() && !meta.getTitle().toLowerCase()
434 .contains(filter.toLowerCase())) {
435 continue;
436 }
437
438 // TODO Sub sources
439 if (filterSource != null
440 && !filterSource.equals(meta.getSource())) {
441 continue;
442 }
443
444 // TODO: sub authors
445 if (filterAuthor != null
446 && !filterAuthor.equals(meta.getAuthor())) {
447 continue;
448 }
449
450 if (filterTag != null && !meta.getTags().contains(filterTag)) {
451 continue;
452 }
453
454 String author = "";
455 if (meta.getAuthor() != null && !meta.getAuthor().isEmpty()) {
456 author = "(" + meta.getAuthor() + ")";
457 }
458
459 booklines.add(templates.bookline( //
460 meta.getLuid(), //
461 WebLibraryUrls.getViewUrl(meta.getLuid(), null, null), //
462 meta.getTitle(), //
463 author, //
464 lib.isCached(meta.getLuid()) //
465 ));
466 }
467
468 // Add the browser in front of the booklines
469 booklines.add(0, templates.browser(browser, filter, selects));
470
471 return newInputStreamResponse(NanoHTTPD.MIME_HTML,
472 templates.index(true, booklines).read());
473 }
474
475 private Response getViewer(Map<String, String> cookies, String uri,
476 WLoginResult login) {
477 String[] cover = uri.split("/");
478 int off = 2;
479
480 if (cover.length < off + 2) {
481 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
482 NanoHTTPD.MIME_PLAINTEXT, null);
483 }
484
485 String type = cover[off + 0];
486 String luid = cover[off + 1];
487 String chapterStr = cover.length < off + 3 ? null : cover[off + 2];
488 String paragraphStr = cover.length < off + 4 ? null : cover[off + 3];
489
490 // 1-based (0 = desc)
491 int chapter = 0;
492 if (chapterStr != null) {
493 try {
494 chapter = Integer.parseInt(chapterStr);
495 if (chapter < 0) {
496 throw new NumberFormatException();
497 }
498 } catch (NumberFormatException e) {
499 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
500 NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid");
501 }
502 }
503
504 // 1-based
505 int paragraph = 0;
506 if (paragraphStr != null) {
507 try {
508 paragraph = Integer.parseInt(paragraphStr);
509 if (paragraph <= 0) {
510 throw new NumberFormatException();
511 }
512 } catch (NumberFormatException e) {
513 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
514 NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid");
515 }
516 }
517
518 try {
519 Story story = story(luid, login);
520 if (story == null) {
521 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
522 NanoHTTPD.MIME_PLAINTEXT, "Story not found");
523 }
524
525 // For images documents, always go to the images if not chap 0 desc
526 if (story.getMeta().isImageDocument()) {
527 if (chapter > 0 && paragraph <= 0)
528 paragraph = 1;
529 }
530
531 Chapter chap = null;
532 if (chapter <= 0) {
533 chap = story.getMeta().getResume();
534 } else {
535 try {
536 chap = story.getChapters().get(chapter - 1);
537 } catch (IndexOutOfBoundsException e) {
538 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
539 NanoHTTPD.MIME_PLAINTEXT, "Chapter not found");
540 }
541 }
542
543 String first, previous, next, last;
544
545 boolean disabledLeft = false;
546 boolean disabledRight = false;
547 boolean disabledZoomReal = false;
548 boolean disabledZoomWidth = false;
549 boolean disabledZoomHeight = false;
550
551 Template viewerItem = null;
552 if (paragraph <= 0) {
553 first = WebLibraryUrls.getViewUrl(luid, 0, null);
554 previous = WebLibraryUrls.getViewUrl(luid,
555 (Math.max(chapter - 1, 0)), null);
556 next = WebLibraryUrls.getViewUrl(luid,
557 (Math.min(chapter + 1, story.getChapters().size())),
558 null);
559 last = WebLibraryUrls.getViewUrl(luid,
560 story.getChapters().size(), null);
561
562 Template desc = null;
563 if (chapter <= 0) {
564 List<Template> desclines = new ArrayList<Template>();
565 Map<String, String> details = BasicLibrary
566 .getMetaDesc(story.getMeta());
567 for (String key : details.keySet()) {
568 desclines.add(templates.viewerDescline(key,
569 details.get(key)));
570 }
571
572 desc = templates.viewerDesc( //
573 story.getMeta().getTitle(), //
574 next, //
575 WebLibraryUrls.getStoryUrlCover(luid), //
576 desclines //
577 );
578 }
579
580 String content;
581 if (chap.getParagraphs().size() <= 0) {
582 content = "No content provided.";
583 } else {
584 content = new TextOutput(false).convert(chap, chapter > 0);
585 }
586
587 viewerItem = templates.viewerText(desc, content);
588
589 if (chapter <= 0)
590 disabledLeft = true;
591 if (chapter >= story.getChapters().size())
592 disabledRight = true;
593 } else {
594 first = WebLibraryUrls.getViewUrl(luid, chapter, 1);
595 previous = WebLibraryUrls.getViewUrl(luid, chapter,
596 (Math.max(paragraph - 1, 1)));
597 next = WebLibraryUrls.getViewUrl(luid, chapter,
598 (Math.min(paragraph + 1, chap.getParagraphs().size())));
599 last = WebLibraryUrls.getViewUrl(luid, chapter,
600 chap.getParagraphs().size());
601
602 if (paragraph <= 1)
603 disabledLeft = true;
604 if (paragraph >= chap.getParagraphs().size())
605 disabledRight = true;
606
607 // First -> previous *chapter*
608 if (chapter > 0)
609 disabledLeft = false;
610 first = WebLibraryUrls.getViewUrl(luid,
611 (Math.max(chapter - 1, 0)), null);
612 if (paragraph <= 1) {
613 previous = first;
614 }
615
616 Paragraph para = null;
617 try {
618 para = chap.getParagraphs().get(paragraph - 1);
619 } catch (IndexOutOfBoundsException e) {
620 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
621 NanoHTTPD.MIME_PLAINTEXT,
622 "Paragraph " + paragraph + " not found");
623 }
624
625 if (para.getType() == ParagraphType.IMAGE) {
626 String zoomStyle = "max-width: 100%;";
627 disabledZoomWidth = true;
628 String zoomOption = cookies.get("zoom");
629 if (zoomOption != null && !zoomOption.isEmpty()) {
630 if (zoomOption.equals("real")) {
631 zoomStyle = "";
632 disabledZoomWidth = false;
633 disabledZoomReal = true;
634 } else if (zoomOption.equals("width")) {
635 zoomStyle = "max-width: 100%;";
636 } else if (zoomOption.equals("height")) {
637 // see height of navbar + optionbar
638 zoomStyle = "max-height: calc(100% - 128px);";
639 disabledZoomWidth = false;
640 disabledZoomHeight = true;
641 }
642 }
643
644 viewerItem = templates.viewerImage(WebLibraryUrls
645 .getStoryUrl(luid, chapter, paragraph), next,
646 zoomStyle);
647 } else {
648 viewerItem = templates.viewerText(null,
649 new TextOutput(false).convert(para));
650 }
651 }
652
653 // List of chap/para links for navbar
654
655 List<Template> links = new ArrayList<Template>();
656 links.add(templates.viewerLink( //
657 "Description", //
658 WebLibraryUrls.getViewUrl(luid, 0, null), //
659 paragraph == 0 && chapter == 0 //
660 ));
661 if (paragraph > 0) {
662 for (int i = 1; i <= chap.getParagraphs().size(); i++) {
663 links.add(templates.viewerLink( //
664 "Image " + i, //
665 WebLibraryUrls.getViewUrl(luid, chapter, i), //
666 paragraph == i //
667 ));
668 }
669 } else {
670 int i = 1;
671 for (Chapter c : story.getChapters()) {
672 String chapName = "Chapter " + c.getNumber();
673 if (c.getName() != null && !c.getName().isEmpty()) {
674 chapName += ": " + c.getName();
675 }
676
677 links.add(templates.viewerLink( //
678 chapName, //
679 WebLibraryUrls.getViewUrl(luid, i, null), //
680 chapter == i //
681 ));
682
683 i++;
684 }
685 }
686
687 // Navbar
688
689 Template navbar = templates.viewerNavbar( //
690 paragraph > 0 ? paragraph : chapter, //
691 links, //
692 first, //
693 previous, //
694 next, //
695 last, //
696 disabledLeft, //
697 disabledLeft, //
698 disabledRight, //
699 disabledRight //
700 );
701
702 // Buttons on the optionbar
703
704 List<Template> buttons = new ArrayList<Template>();
705 buttons.add(templates.viewerOptionbarButton( //
706 "Back", "/", "back", false));
707 if (paragraph > 0) {
708 buttons.add(templates.viewerOptionbarButton( //
709 "1:1", uri + "?optionName=zoom&optionValue=real",
710 "zoomreal", disabledZoomReal));
711 buttons.add(templates.viewerOptionbarButton( //
712 "Width", uri + "?optionName=zoom&optionValue=width",
713 "zoomwidth", disabledZoomWidth));
714 buttons.add(templates.viewerOptionbarButton( //
715 "Height", uri + "?optionName=zoom&optionValue=height",
716 "zoomHeight", disabledZoomHeight));
717 }
718
719 // Optionbar
720
721 Template optionbar = templates.viewerOptionbar( //
722 (paragraph > 0) ? 4 : 1, //
723 buttons //
724 );
725
726 // Full content
727
728 return newInputStreamResponse(NanoHTTPD.MIME_HTML, //
729 templates.index(false, Arrays.asList( //
730 navbar, //
731 viewerItem, //
732 optionbar //
733 )).read());
734 } catch (IOException e) {
735 Instance.getInstance().getTraceHandler()
736 .error(new IOException("Cannot get image: " + uri, e));
737 return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR,
738 NanoHTTPD.MIME_PLAINTEXT, "Error when processing request");
739 }
740 }
741
742 protected Response newInputStreamResponse(String mimeType, InputStream in) {
743 if (in == null) {
744 return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "",
745 null);
746 }
747 return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in);
748 }
749 }