weblib: add mimetypes for ico/png/java
[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 } else if (uri.endsWith(".png")) {
251 mimeType = "image/png";
252 } else if (uri.endsWith(".ico")) {
253 mimeType = "image/x-icon";
254 } else if (uri.endsWith(".java")) {
255 mimeType = "text/plain";
256 }
257 rep = newChunkedResponse(Status.OK, mimeType,
258 in);
259 }
260
261 if (rep == null) {
262 getTraceHandler().trace("404: " + uri);
263 rep = newFixedLengthResponse(Status.NOT_FOUND,
264 NanoHTTPD.MIME_PLAINTEXT, "Not Found");
265 }
266 }
267 }
268 } catch (Exception e) {
269 Instance.getInstance().getTraceHandler().error(
270 new IOException("Cannot process web request", e));
271 rep = newFixedLengthResponse(Status.INTERNAL_ERROR,
272 NanoHTTPD.MIME_PLAINTEXT, "An error occured");
273 }
274
275 return rep;
276 }
277 };
278
279 if (ssf != null) {
280 getTraceHandler().trace("Install SSL on the web server...");
281 server.makeSecure(ssf, null);
282 getTraceHandler().trace("Done.");
283 }
284 }
285
286 @Override
287 public void run() {
288 try {
289 server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
290 } catch (IOException e) {
291 tracer.error(new IOException("Cannot start the web server", e));
292 }
293 }
294
295 protected void doStop() {
296 server.stop();
297 }
298
299 /**
300 * The traces handler for this {@link WebLibraryServerHtml}.
301 *
302 * @return the traces handler
303 */
304 public TraceHandler getTraceHandler() {
305 return tracer;
306 }
307
308 /**
309 * The traces handler for this {@link WebLibraryServerHtml}.
310 *
311 * @param tracer
312 * the new traces handler
313 */
314 public void setTraceHandler(TraceHandler tracer) {
315 if (tracer == null) {
316 tracer = new TraceHandler(false, false, false);
317 }
318
319 this.tracer = tracer;
320 }
321
322 private Response loginPage(WLoginResult login, String uri)
323 throws IOException {
324 List<Template> content = new ArrayList<Template>();
325
326 if (login.isBadLogin()) {
327 content.add(templates.message("Bad login or password", true));
328 } else if (login.isBadCookie()) {
329 content.add(templates.message("Your session timed out", true));
330 }
331
332 content.add(templates.login(uri));
333
334 return NanoHTTPD.newChunkedResponse(Status.FORBIDDEN,
335 NanoHTTPD.MIME_HTML, templates.index(true, content).read());
336 }
337
338 private Response root(IHTTPSession session, Map<String, String> cookies,
339 WLoginResult login) throws IOException {
340 BasicLibrary lib = Instance.getInstance().getLibrary();
341 MetaResultList result = new MetaResultList(metas(login));
342
343 Map<String, String> params = session.getParms();
344
345 String filter = cookies.get("filter");
346 if (params.get("optionNo") != null)
347 filter = null;
348 if (filter == null) {
349 filter = "";
350 }
351
352 String browser = params.get("browser") == null ? ""
353 : params.get("browser");
354 String browser2 = params.get("browser2") == null ? ""
355 : params.get("browser2");
356 String browser3 = params.get("browser3") == null ? ""
357 : params.get("browser3");
358
359 String filterSource = null;
360 String filterAuthor = null;
361 String filterTag = null;
362
363 // TODO: javascript in realtime, using visible=false + hide [submit]
364
365 List<Template> selects = new ArrayList<Template>();
366 boolean sourcesSel = false;
367 boolean authorsSel = false;
368 boolean tagsSel = false;
369
370 if (!browser.isEmpty()) {
371 List<Template> options = new ArrayList<Template>();
372
373 if (browser.equals("sources")) {
374 sourcesSel = true;
375 filterSource = browser2.isEmpty() ? filterSource : browser2;
376
377 // TODO: if 1 group -> no group
378 Map<String, List<String>> sources = result.getSourcesGrouped();
379 for (String source : sources.keySet()) {
380 options.add(
381 templates.browserOption(source, source, browser2));
382 }
383 } else if (browser.equals("authors")) {
384 authorsSel = true;
385 filterAuthor = browser2.isEmpty() ? filterAuthor : browser2;
386
387 // TODO: if 1 group -> no group
388 Map<String, List<String>> authors = result.getAuthorsGrouped();
389 for (String author : authors.keySet()) {
390 options.add(
391 templates.browserOption(author, author, browser2));
392 }
393 } else if (browser.equals("tags")) {
394 tagsSel = true;
395 filterTag = browser2.isEmpty() ? filterTag : browser2;
396
397 for (String tag : result.getTags()) {
398 options.add(templates.browserOption(tag, tag, browser2));
399 }
400 }
401
402 selects.add(templates.browserSelect("browser2", browser2, options));
403 }
404
405 if (!browser2.isEmpty()) {
406 List<Template> options = new ArrayList<Template>();
407
408 if (browser.equals("sources")) {
409 filterSource = browser3.isEmpty() ? filterSource : browser3;
410 Map<String, List<String>> sourcesGrouped = result
411 .getSourcesGrouped();
412 List<String> sources = sourcesGrouped.get(browser2);
413 if (sources != null && !sources.isEmpty()) {
414 // TODO: single empty value
415 for (String source : sources) {
416 options.add(templates.browserOption(source, source,
417 browser3));
418 }
419 }
420 } else if (browser.equals("authors")) {
421 filterAuthor = browser3.isEmpty() ? filterAuthor : browser3;
422 Map<String, List<String>> authorsGrouped = result
423 .getAuthorsGrouped();
424 List<String> authors = authorsGrouped.get(browser2);
425 if (authors != null && !authors.isEmpty()) {
426 // TODO: single empty value
427 for (String author : authors) {
428 options.add(templates.browserOption(author, author,
429 browser3));
430 }
431 }
432 }
433
434 selects.add(templates.browserSelect("browser3", browser3, options));
435 }
436
437 List<Template> booklines = new ArrayList<Template>();
438 for (MetaData meta : result.getMetas()) {
439 if (!filter.isEmpty() && !meta.getTitle().toLowerCase()
440 .contains(filter.toLowerCase())) {
441 continue;
442 }
443
444 // TODO Sub sources
445 if (filterSource != null
446 && !filterSource.equals(meta.getSource())) {
447 continue;
448 }
449
450 // TODO: sub authors
451 if (filterAuthor != null
452 && !filterAuthor.equals(meta.getAuthor())) {
453 continue;
454 }
455
456 if (filterTag != null && !meta.getTags().contains(filterTag)) {
457 continue;
458 }
459
460 String author = "";
461 if (meta.getAuthor() != null && !meta.getAuthor().isEmpty()) {
462 author = "(" + meta.getAuthor() + ")";
463 }
464
465 booklines.add(templates.bookline( //
466 meta.getLuid(), //
467 WebLibraryUrls.getViewUrl(meta.getLuid(), null, null), //
468 meta.getTitle(), //
469 author, //
470 lib.isCached(meta.getLuid()) //
471 ));
472 }
473
474 // Add the browser in front of the booklines
475 booklines.add(0, templates.browser(browser, filter, selects));
476
477 return newInputStreamResponse(NanoHTTPD.MIME_HTML,
478 templates.index(true, booklines).read());
479 }
480
481 private Response getViewer(Map<String, String> cookies, String uri,
482 WLoginResult login) {
483 String[] cover = uri.split("/");
484 int off = 2;
485
486 if (cover.length < off + 2) {
487 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
488 NanoHTTPD.MIME_PLAINTEXT, null);
489 }
490
491 String type = cover[off + 0];
492 String luid = cover[off + 1];
493 String chapterStr = cover.length < off + 3 ? null : cover[off + 2];
494 String paragraphStr = cover.length < off + 4 ? null : cover[off + 3];
495
496 // 1-based (0 = desc)
497 int chapter = 0;
498 if (chapterStr != null) {
499 try {
500 chapter = Integer.parseInt(chapterStr);
501 if (chapter < 0) {
502 throw new NumberFormatException();
503 }
504 } catch (NumberFormatException e) {
505 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
506 NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid");
507 }
508 }
509
510 // 1-based
511 int paragraph = 0;
512 if (paragraphStr != null) {
513 try {
514 paragraph = Integer.parseInt(paragraphStr);
515 if (paragraph <= 0) {
516 throw new NumberFormatException();
517 }
518 } catch (NumberFormatException e) {
519 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
520 NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid");
521 }
522 }
523
524 try {
525 Story story = story(luid, login);
526 if (story == null) {
527 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
528 NanoHTTPD.MIME_PLAINTEXT, "Story not found");
529 }
530
531 // For images documents, always go to the images if not chap 0 desc
532 if (story.getMeta().isImageDocument()) {
533 if (chapter > 0 && paragraph <= 0)
534 paragraph = 1;
535 }
536
537 Chapter chap = null;
538 if (chapter <= 0) {
539 chap = story.getMeta().getResume();
540 } else {
541 try {
542 chap = story.getChapters().get(chapter - 1);
543 } catch (IndexOutOfBoundsException e) {
544 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
545 NanoHTTPD.MIME_PLAINTEXT, "Chapter not found");
546 }
547 }
548
549 String first, previous, next, last;
550
551 boolean disabledLeft = false;
552 boolean disabledRight = false;
553 boolean disabledZoomReal = false;
554 boolean disabledZoomWidth = false;
555 boolean disabledZoomHeight = false;
556
557 Template viewerItem = null;
558 if (paragraph <= 0) {
559 first = WebLibraryUrls.getViewUrl(luid, 0, null);
560 previous = WebLibraryUrls.getViewUrl(luid,
561 (Math.max(chapter - 1, 0)), null);
562 next = WebLibraryUrls.getViewUrl(luid,
563 (Math.min(chapter + 1, story.getChapters().size())),
564 null);
565 last = WebLibraryUrls.getViewUrl(luid,
566 story.getChapters().size(), null);
567
568 Template desc = null;
569 if (chapter <= 0) {
570 List<Template> desclines = new ArrayList<Template>();
571 Map<String, String> details = BasicLibrary
572 .getMetaDesc(story.getMeta());
573 for (String key : details.keySet()) {
574 desclines.add(templates.viewerDescline(key,
575 details.get(key)));
576 }
577
578 desc = templates.viewerDesc( //
579 story.getMeta().getTitle(), //
580 next, //
581 WebLibraryUrls.getStoryUrlCover(luid), //
582 desclines //
583 );
584 }
585
586 String content;
587 if (chap.getParagraphs().size() <= 0) {
588 content = "No content provided.";
589 } else {
590 content = new TextOutput(false).convert(chap, chapter > 0);
591 }
592
593 viewerItem = templates.viewerText(desc, content);
594
595 if (chapter <= 0)
596 disabledLeft = true;
597 if (chapter >= story.getChapters().size())
598 disabledRight = true;
599 } else {
600 first = WebLibraryUrls.getViewUrl(luid, chapter, 1);
601 previous = WebLibraryUrls.getViewUrl(luid, chapter,
602 (Math.max(paragraph - 1, 1)));
603 next = WebLibraryUrls.getViewUrl(luid, chapter,
604 (Math.min(paragraph + 1, chap.getParagraphs().size())));
605 last = WebLibraryUrls.getViewUrl(luid, chapter,
606 chap.getParagraphs().size());
607
608 if (paragraph <= 1)
609 disabledLeft = true;
610 if (paragraph >= chap.getParagraphs().size())
611 disabledRight = true;
612
613 // First -> previous *chapter*
614 if (chapter > 0)
615 disabledLeft = false;
616 first = WebLibraryUrls.getViewUrl(luid,
617 (Math.max(chapter - 1, 0)), null);
618 if (paragraph <= 1) {
619 previous = first;
620 }
621
622 Paragraph para = null;
623 try {
624 para = chap.getParagraphs().get(paragraph - 1);
625 } catch (IndexOutOfBoundsException e) {
626 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
627 NanoHTTPD.MIME_PLAINTEXT,
628 "Paragraph " + paragraph + " not found");
629 }
630
631 if (para.getType() == ParagraphType.IMAGE) {
632 String zoomStyle = "max-width: 100%;";
633 disabledZoomWidth = true;
634 String zoomOption = cookies.get("zoom");
635 if (zoomOption != null && !zoomOption.isEmpty()) {
636 if (zoomOption.equals("real")) {
637 zoomStyle = "";
638 disabledZoomWidth = false;
639 disabledZoomReal = true;
640 } else if (zoomOption.equals("width")) {
641 zoomStyle = "max-width: 100%;";
642 } else if (zoomOption.equals("height")) {
643 // see height of navbar + optionbar
644 zoomStyle = "max-height: calc(100% - 128px);";
645 disabledZoomWidth = false;
646 disabledZoomHeight = true;
647 }
648 }
649
650 viewerItem = templates.viewerImage(WebLibraryUrls
651 .getStoryUrl(luid, chapter, paragraph), next,
652 zoomStyle);
653 } else {
654 viewerItem = templates.viewerText(null,
655 new TextOutput(false).convert(para));
656 }
657 }
658
659 // List of chap/para links for navbar
660
661 List<Template> links = new ArrayList<Template>();
662 links.add(templates.viewerLink( //
663 "Description", //
664 WebLibraryUrls.getViewUrl(luid, 0, null), //
665 paragraph == 0 && chapter == 0 //
666 ));
667 if (paragraph > 0) {
668 for (int i = 1; i <= chap.getParagraphs().size(); i++) {
669 links.add(templates.viewerLink( //
670 "Image " + i, //
671 WebLibraryUrls.getViewUrl(luid, chapter, i), //
672 paragraph == i //
673 ));
674 }
675 } else {
676 int i = 1;
677 for (Chapter c : story.getChapters()) {
678 String chapName = "Chapter " + c.getNumber();
679 if (c.getName() != null && !c.getName().isEmpty()) {
680 chapName += ": " + c.getName();
681 }
682
683 links.add(templates.viewerLink( //
684 chapName, //
685 WebLibraryUrls.getViewUrl(luid, i, null), //
686 chapter == i //
687 ));
688
689 i++;
690 }
691 }
692
693 // Navbar
694
695 Template navbar = templates.viewerNavbar( //
696 paragraph > 0 ? paragraph : chapter, //
697 links, //
698 first, //
699 previous, //
700 next, //
701 last, //
702 disabledLeft, //
703 disabledLeft, //
704 disabledRight, //
705 disabledRight //
706 );
707
708 // Buttons on the optionbar
709
710 List<Template> buttons = new ArrayList<Template>();
711 buttons.add(templates.viewerOptionbarButton( //
712 "Back", "/", "back", false));
713 if (paragraph > 0) {
714 buttons.add(templates.viewerOptionbarButton( //
715 "1:1", uri + "?optionName=zoom&optionValue=real",
716 "zoomreal", disabledZoomReal));
717 buttons.add(templates.viewerOptionbarButton( //
718 "Width", uri + "?optionName=zoom&optionValue=width",
719 "zoomwidth", disabledZoomWidth));
720 buttons.add(templates.viewerOptionbarButton( //
721 "Height", uri + "?optionName=zoom&optionValue=height",
722 "zoomHeight", disabledZoomHeight));
723 }
724
725 // Optionbar
726
727 Template optionbar = templates.viewerOptionbar( //
728 (paragraph > 0) ? 4 : 1, //
729 buttons //
730 );
731
732 // Full content
733
734 return newInputStreamResponse(NanoHTTPD.MIME_HTML, //
735 templates.index(false, Arrays.asList( //
736 navbar, //
737 viewerItem, //
738 optionbar //
739 )).read());
740 } catch (IOException e) {
741 Instance.getInstance().getTraceHandler()
742 .error(new IOException("Cannot get image: " + uri, e));
743 return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR,
744 NanoHTTPD.MIME_PLAINTEXT, "Error when processing request");
745 }
746 }
747
748 protected Response newInputStreamResponse(String mimeType, InputStream in) {
749 if (in == null) {
750 return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "",
751 null);
752 }
753 return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in);
754 }
755 }