weblib: zoomwidth max to 1:1
[fanfix.git] / src / be / nikiroo / fanfix / library / WebLibraryServerHtml.java
CommitLineData
33d40b9f
NR
1package be.nikiroo.fanfix.library;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.IOException;
6import java.io.InputStream;
7import java.security.KeyStore;
8639c60d
NR
8import java.util.ArrayList;
9import java.util.Arrays;
33d40b9f
NR
10import java.util.HashMap;
11import java.util.List;
12import java.util.Map;
13
14import javax.net.ssl.KeyManagerFactory;
15import javax.net.ssl.SSLServerSocketFactory;
16
17import be.nikiroo.fanfix.Instance;
18import be.nikiroo.fanfix.bundles.Config;
33d40b9f
NR
19import be.nikiroo.fanfix.data.Chapter;
20import be.nikiroo.fanfix.data.MetaData;
21import be.nikiroo.fanfix.data.Paragraph;
22import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
23import be.nikiroo.fanfix.data.Story;
24import be.nikiroo.fanfix.library.WebLibraryServer.WLoginResult;
25import be.nikiroo.fanfix.library.web.WebLibraryServerIndex;
83001824 26import be.nikiroo.fanfix.library.web.templates.WebLibraryServerTemplates;
33d40b9f
NR
27import be.nikiroo.fanfix.reader.TextOutput;
28import be.nikiroo.utils.IOUtils;
29import be.nikiroo.utils.NanoHTTPD;
30import be.nikiroo.utils.NanoHTTPD.IHTTPSession;
31import be.nikiroo.utils.NanoHTTPD.Response;
32import be.nikiroo.utils.NanoHTTPD.Response.Status;
33import be.nikiroo.utils.TraceHandler;
34import be.nikiroo.utils.Version;
35
36abstract class WebLibraryServerHtml implements Runnable {
37 private NanoHTTPD server;
38 protected TraceHandler tracer = new TraceHandler();
39
8639c60d
NR
40 WebLibraryServerTemplates templates = WebLibraryServerTemplates
41 .getInstance();
42
33d40b9f
NR
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
089e354e
NR
55 abstract protected Response setStoryPart(String uri, String value,
56 WLoginResult login) throws IOException;
57
6673ec59
NR
58 abstract protected Response getCover(String uri, WLoginResult login)
59 throws IOException;
60
e4b1b70c
NR
61 abstract protected Response setCover(String uri, String luid,
62 WLoginResult login) throws IOException;
63
33d40b9f
NR
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
f70bcacf
NR
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
a1226ce0
NR
75 protected abstract Response delete(String uri, WLoginResult login)
76 throws IOException;
77
09c2396e
NR
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
33d40b9f
NR
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;
83001824
NR
181 try {
182 if (!login.isSuccess()
183 && WebLibraryUrls.isSupportedUrl(uri, true)) {
184 rep = loginPage(login, uri);
185 }
33d40b9f 186
83001824 187 if (rep == null) {
44f134a5 188 if (WebLibraryUrls.isSupportedUrl(uri, false)) {
33d40b9f
NR
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());
6673ec59 195 } else if (WebLibraryUrls.isCoverUrl(uri)) {
e4b1b70c
NR
196 String luid = params.get("luid");
197 if (luid != null) {
198 rep = setCover(uri, luid, login);
199 } else {
200 rep = getCover(uri, login);
201 }
33d40b9f
NR
202 } else if (WebLibraryUrls.isListUrl(uri)) {
203 rep = getList(uri, login);
204 } else if (WebLibraryUrls.isStoryUrl(uri)) {
089e354e
NR
205 String value = params.get("value");
206 if (value != null) {
207 rep = setStoryPart(uri, value, login);
208 } else {
209 rep = getStoryPart(uri, login);
210 }
33d40b9f
NR
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);
f70bcacf
NR
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 }
a1226ce0
NR
224 } else if (WebLibraryUrls.isDeleteUrl(uri)) {
225 rep = delete(uri, login);
09c2396e
NR
226 } else if (WebLibraryUrls.EXIT_URL.equals(uri)) {
227 rep = WebLibraryServerHtml.this.stop(login);
33d40b9f
NR
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";
8c7f2a44
NR
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";
33d40b9f
NR
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 }
33d40b9f 267 }
83001824
NR
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");
33d40b9f
NR
273 }
274
275 return rep;
276 }
277 };
278
acbec0d2 279 if (ssf != null) {
33d40b9f
NR
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
09c2396e
NR
295 protected void doStop() {
296 server.stop();
297 }
298
33d40b9f
NR
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
83001824
NR
322 private Response loginPage(WLoginResult login, String uri)
323 throws IOException {
074a8325 324 List<Template> content = new ArrayList<Template>();
33d40b9f
NR
325
326 if (login.isBadLogin()) {
074a8325 327 content.add(templates.message("Bad login or password", true));
33d40b9f 328 } else if (login.isBadCookie()) {
074a8325 329 content.add(templates.message("Your session timed out", true));
33d40b9f
NR
330 }
331
074a8325 332 content.add(templates.login(uri));
33d40b9f 333
074a8325 334 return NanoHTTPD.newChunkedResponse(Status.FORBIDDEN,
a1ddc982
NR
335 NanoHTTPD.MIME_HTML,
336 templates.index(true, false, content).read());
33d40b9f
NR
337 }
338
339 private Response root(IHTTPSession session, Map<String, String> cookies,
340 WLoginResult login) throws IOException {
341 BasicLibrary lib = Instance.getInstance().getLibrary();
342 MetaResultList result = new MetaResultList(metas(login));
33d40b9f
NR
343
344 Map<String, String> params = session.getParms();
345
346 String filter = cookies.get("filter");
347 if (params.get("optionNo") != null)
348 filter = null;
349 if (filter == null) {
350 filter = "";
351 }
352
353 String browser = params.get("browser") == null ? ""
354 : params.get("browser");
355 String browser2 = params.get("browser2") == null ? ""
356 : params.get("browser2");
357 String browser3 = params.get("browser3") == null ? ""
358 : params.get("browser3");
359
360 String filterSource = null;
361 String filterAuthor = null;
362 String filterTag = null;
363
364 // TODO: javascript in realtime, using visible=false + hide [submit]
365
8639c60d 366 List<Template> selects = new ArrayList<Template>();
c4361515
NR
367 boolean sourcesSel = false;
368 boolean authorsSel = false;
369 boolean tagsSel = false;
33d40b9f 370
33d40b9f 371 if (!browser.isEmpty()) {
8639c60d 372 List<Template> options = new ArrayList<Template>();
c4361515 373
33d40b9f 374 if (browser.equals("sources")) {
c4361515 375 sourcesSel = true;
33d40b9f 376 filterSource = browser2.isEmpty() ? filterSource : browser2;
c4361515 377
33d40b9f 378 // TODO: if 1 group -> no group
33d40b9f
NR
379 Map<String, List<String>> sources = result.getSourcesGrouped();
380 for (String source : sources.keySet()) {
8639c60d
NR
381 options.add(
382 templates.browserOption(source, source, browser2));
33d40b9f
NR
383 }
384 } else if (browser.equals("authors")) {
c4361515 385 authorsSel = true;
33d40b9f 386 filterAuthor = browser2.isEmpty() ? filterAuthor : browser2;
c4361515 387
33d40b9f 388 // TODO: if 1 group -> no group
33d40b9f
NR
389 Map<String, List<String>> authors = result.getAuthorsGrouped();
390 for (String author : authors.keySet()) {
8639c60d
NR
391 options.add(
392 templates.browserOption(author, author, browser2));
33d40b9f
NR
393 }
394 } else if (browser.equals("tags")) {
c4361515 395 tagsSel = true;
33d40b9f 396 filterTag = browser2.isEmpty() ? filterTag : browser2;
c4361515 397
33d40b9f 398 for (String tag : result.getTags()) {
8639c60d 399 options.add(templates.browserOption(tag, tag, browser2));
33d40b9f
NR
400 }
401 }
c4361515 402
8639c60d 403 selects.add(templates.browserSelect("browser2", browser2, options));
33d40b9f
NR
404 }
405
406 if (!browser2.isEmpty()) {
8639c60d 407 List<Template> options = new ArrayList<Template>();
c4361515 408
33d40b9f
NR
409 if (browser.equals("sources")) {
410 filterSource = browser3.isEmpty() ? filterSource : browser3;
411 Map<String, List<String>> sourcesGrouped = result
412 .getSourcesGrouped();
413 List<String> sources = sourcesGrouped.get(browser2);
414 if (sources != null && !sources.isEmpty()) {
415 // TODO: single empty value
33d40b9f 416 for (String source : sources) {
8639c60d 417 options.add(templates.browserOption(source, source,
83001824 418 browser3));
33d40b9f 419 }
33d40b9f
NR
420 }
421 } else if (browser.equals("authors")) {
422 filterAuthor = browser3.isEmpty() ? filterAuthor : browser3;
423 Map<String, List<String>> authorsGrouped = result
424 .getAuthorsGrouped();
425 List<String> authors = authorsGrouped.get(browser2);
426 if (authors != null && !authors.isEmpty()) {
427 // TODO: single empty value
33d40b9f 428 for (String author : authors) {
8639c60d 429 options.add(templates.browserOption(author, author,
83001824 430 browser3));
33d40b9f 431 }
33d40b9f
NR
432 }
433 }
33d40b9f 434
8639c60d 435 selects.add(templates.browserSelect("browser3", browser3, options));
c4361515 436 }
33d40b9f 437
8639c60d 438 List<Template> booklines = new ArrayList<Template>();
33d40b9f
NR
439 for (MetaData meta : result.getMetas()) {
440 if (!filter.isEmpty() && !meta.getTitle().toLowerCase()
441 .contains(filter.toLowerCase())) {
442 continue;
443 }
444
445 // TODO Sub sources
446 if (filterSource != null
447 && !filterSource.equals(meta.getSource())) {
448 continue;
449 }
450
451 // TODO: sub authors
452 if (filterAuthor != null
453 && !filterAuthor.equals(meta.getAuthor())) {
454 continue;
455 }
456
457 if (filterTag != null && !meta.getTags().contains(filterTag)) {
458 continue;
459 }
460
c4361515 461 String author = "";
33d40b9f 462 if (meta.getAuthor() != null && !meta.getAuthor().isEmpty()) {
c4361515
NR
463 author = "(" + meta.getAuthor() + ")";
464 }
465
8639c60d
NR
466 booklines.add(templates.bookline( //
467 meta.getLuid(), //
468 WebLibraryUrls.getViewUrl(meta.getLuid(), null, null), //
469 meta.getTitle(), //
470 author, //
471 lib.isCached(meta.getLuid()) //
472 ));
33d40b9f 473 }
83001824 474
8639c60d
NR
475 // Add the browser in front of the booklines
476 booklines.add(0, templates.browser(browser, filter, selects));
074a8325 477
8639c60d 478 return newInputStreamResponse(NanoHTTPD.MIME_HTML,
a1ddc982 479 templates.index(true, false, booklines).read());
33d40b9f
NR
480 }
481
482 private Response getViewer(Map<String, String> cookies, String uri,
483 WLoginResult login) {
484 String[] cover = uri.split("/");
485 int off = 2;
486
487 if (cover.length < off + 2) {
488 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
489 NanoHTTPD.MIME_PLAINTEXT, null);
490 }
491
492 String type = cover[off + 0];
493 String luid = cover[off + 1];
494 String chapterStr = cover.length < off + 3 ? null : cover[off + 2];
495 String paragraphStr = cover.length < off + 4 ? null : cover[off + 3];
496
497 // 1-based (0 = desc)
498 int chapter = 0;
499 if (chapterStr != null) {
500 try {
501 chapter = Integer.parseInt(chapterStr);
502 if (chapter < 0) {
503 throw new NumberFormatException();
504 }
505 } catch (NumberFormatException e) {
506 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
507 NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid");
508 }
509 }
510
511 // 1-based
512 int paragraph = 0;
513 if (paragraphStr != null) {
514 try {
515 paragraph = Integer.parseInt(paragraphStr);
516 if (paragraph <= 0) {
517 throw new NumberFormatException();
518 }
519 } catch (NumberFormatException e) {
520 return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
521 NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid");
522 }
523 }
524
525 try {
526 Story story = story(luid, login);
527 if (story == null) {
528 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
529 NanoHTTPD.MIME_PLAINTEXT, "Story not found");
530 }
531
33d40b9f
NR
532 // For images documents, always go to the images if not chap 0 desc
533 if (story.getMeta().isImageDocument()) {
534 if (chapter > 0 && paragraph <= 0)
535 paragraph = 1;
536 }
537
538 Chapter chap = null;
539 if (chapter <= 0) {
540 chap = story.getMeta().getResume();
541 } else {
542 try {
543 chap = story.getChapters().get(chapter - 1);
544 } catch (IndexOutOfBoundsException e) {
545 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
546 NanoHTTPD.MIME_PLAINTEXT, "Chapter not found");
547 }
548 }
549
550 String first, previous, next, last;
551
1f1318b8
NR
552 boolean disabledLeft = false;
553 boolean disabledRight = false;
554 boolean disabledZoomReal = false;
555 boolean disabledZoomWidth = false;
a1ddc982 556 boolean disabledZoomWidthLimited = false;
1f1318b8 557 boolean disabledZoomHeight = false;
33d40b9f 558
1f1318b8 559 Template viewerItem = null;
33d40b9f
NR
560 if (paragraph <= 0) {
561 first = WebLibraryUrls.getViewUrl(luid, 0, null);
562 previous = WebLibraryUrls.getViewUrl(luid,
563 (Math.max(chapter - 1, 0)), null);
564 next = WebLibraryUrls.getViewUrl(luid,
565 (Math.min(chapter + 1, story.getChapters().size())),
566 null);
567 last = WebLibraryUrls.getViewUrl(luid,
568 story.getChapters().size(), null);
569
1f1318b8 570 Template desc = null;
33d40b9f 571 if (chapter <= 0) {
1f1318b8 572 List<Template> desclines = new ArrayList<Template>();
33d40b9f
NR
573 Map<String, String> details = BasicLibrary
574 .getMetaDesc(story.getMeta());
575 for (String key : details.keySet()) {
1f1318b8
NR
576 desclines.add(templates.viewerDescline(key,
577 details.get(key)));
33d40b9f 578 }
83001824 579
1f1318b8
NR
580 desc = templates.viewerDesc( //
581 story.getMeta().getTitle(), //
582 next, //
583 WebLibraryUrls.getStoryUrlCover(luid), //
584 desclines //
585 );
33d40b9f
NR
586 }
587
1f1318b8 588 String content;
9a705a5a 589 if (chap.getParagraphs().size() <= 0) {
1f1318b8 590 content = "No content provided.";
9a705a5a 591 } else {
1f1318b8 592 content = new TextOutput(false).convert(chap, chapter > 0);
9a705a5a 593 }
33d40b9f 594
1f1318b8
NR
595 viewerItem = templates.viewerText(desc, content);
596
33d40b9f 597 if (chapter <= 0)
1f1318b8 598 disabledLeft = true;
33d40b9f 599 if (chapter >= story.getChapters().size())
1f1318b8 600 disabledRight = true;
33d40b9f
NR
601 } else {
602 first = WebLibraryUrls.getViewUrl(luid, chapter, 1);
603 previous = WebLibraryUrls.getViewUrl(luid, chapter,
604 (Math.max(paragraph - 1, 1)));
605 next = WebLibraryUrls.getViewUrl(luid, chapter,
606 (Math.min(paragraph + 1, chap.getParagraphs().size())));
607 last = WebLibraryUrls.getViewUrl(luid, chapter,
608 chap.getParagraphs().size());
609
610 if (paragraph <= 1)
1f1318b8 611 disabledLeft = true;
33d40b9f 612 if (paragraph >= chap.getParagraphs().size())
1f1318b8 613 disabledRight = true;
33d40b9f
NR
614
615 // First -> previous *chapter*
616 if (chapter > 0)
1f1318b8 617 disabledLeft = false;
33d40b9f
NR
618 first = WebLibraryUrls.getViewUrl(luid,
619 (Math.max(chapter - 1, 0)), null);
620 if (paragraph <= 1) {
621 previous = first;
622 }
623
624 Paragraph para = null;
625 try {
626 para = chap.getParagraphs().get(paragraph - 1);
627 } catch (IndexOutOfBoundsException e) {
628 return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
629 NanoHTTPD.MIME_PLAINTEXT,
630 "Paragraph " + paragraph + " not found");
631 }
632
633 if (para.getType() == ParagraphType.IMAGE) {
a1ddc982
NR
634 // default values:
635 String zoomStyle = "max-width: 800px;";
636 disabledZoomWidthLimited = true;
637
33d40b9f
NR
638 String zoomOption = cookies.get("zoom");
639 if (zoomOption != null && !zoomOption.isEmpty()) {
640 if (zoomOption.equals("real")) {
641 zoomStyle = "";
a1ddc982 642 disabledZoomWidthLimited = false;
1f1318b8 643 disabledZoomReal = true;
a1ddc982
NR
644 } else if (zoomOption.equals("widthlimited")) {
645 // default
33d40b9f 646 } else if (zoomOption.equals("width")) {
bc846414 647 zoomStyle = "max-width: 100%;";
a1ddc982
NR
648 disabledZoomWidthLimited = false;
649 disabledZoomWidth = true;
33d40b9f
NR
650 } else if (zoomOption.equals("height")) {
651 // see height of navbar + optionbar
652 zoomStyle = "max-height: calc(100% - 128px);";
a1ddc982 653 disabledZoomWidthLimited = false;
1f1318b8 654 disabledZoomHeight = true;
33d40b9f
NR
655 }
656 }
657
a1ddc982
NR
658 viewerItem = templates.viewerImage(
659 WebLibraryUrls.getStoryUrl(luid, chapter,
660 paragraph), //
b8b60b32
NR
661 disabledRight ? null : next, //
662 zoomStyle //
a1ddc982 663 );
33d40b9f 664 } else {
1f1318b8
NR
665 viewerItem = templates.viewerText(null,
666 new TextOutput(false).convert(para));
33d40b9f
NR
667 }
668 }
669
1f1318b8
NR
670 // List of chap/para links for navbar
671
672 List<Template> links = new ArrayList<Template>();
673 links.add(templates.viewerLink( //
674 "Description", //
675 WebLibraryUrls.getViewUrl(luid, 0, null), //
676 paragraph == 0 && chapter == 0 //
677 ));
33d40b9f
NR
678 if (paragraph > 0) {
679 for (int i = 1; i <= chap.getParagraphs().size(); i++) {
1f1318b8
NR
680 links.add(templates.viewerLink( //
681 "Image " + i, //
682 WebLibraryUrls.getViewUrl(luid, chapter, i), //
683 paragraph == i //
684 ));
33d40b9f
NR
685 }
686 } else {
687 int i = 1;
688 for (Chapter c : story.getChapters()) {
689 String chapName = "Chapter " + c.getNumber();
690 if (c.getName() != null && !c.getName().isEmpty()) {
691 chapName += ": " + c.getName();
692 }
693
1f1318b8
NR
694 links.add(templates.viewerLink( //
695 chapName, //
696 WebLibraryUrls.getViewUrl(luid, i, null), //
697 chapter == i //
698 ));
33d40b9f
NR
699
700 i++;
701 }
702 }
703
1f1318b8
NR
704 // Navbar
705
706 Template navbar = templates.viewerNavbar( //
707 paragraph > 0 ? paragraph : chapter, //
708 links, //
709 first, //
710 previous, //
711 next, //
712 last, //
713 disabledLeft, //
714 disabledLeft, //
715 disabledRight, //
716 disabledRight //
717 );
718
83001824 719 // Buttons on the optionbar
33d40b9f 720
1f1318b8
NR
721 List<Template> buttons = new ArrayList<Template>();
722 buttons.add(templates.viewerOptionbarButton( //
723 "Back", "/", "back", false));
33d40b9f 724 if (paragraph > 0) {
1f1318b8
NR
725 buttons.add(templates.viewerOptionbarButton( //
726 "1:1", uri + "?optionName=zoom&optionValue=real",
727 "zoomreal", disabledZoomReal));
a1ddc982
NR
728 buttons.add(templates.viewerOptionbarButton( //
729 "]width[",
730 uri + "?optionName=zoom&optionValue=widthlimited",
731 "zoomwidthlimited", disabledZoomWidthLimited));
1f1318b8
NR
732 buttons.add(templates.viewerOptionbarButton( //
733 "Width", uri + "?optionName=zoom&optionValue=width",
734 "zoomwidth", disabledZoomWidth));
735 buttons.add(templates.viewerOptionbarButton( //
736 "Height", uri + "?optionName=zoom&optionValue=height",
a1ddc982 737 "zoomheight", disabledZoomHeight));
33d40b9f 738 }
33d40b9f 739
1f1318b8 740 // Optionbar
83001824 741
1f1318b8 742 Template optionbar = templates.viewerOptionbar( //
a1ddc982 743 (paragraph > 0) ? 5 : 1, //
1f1318b8 744 buttons //
83001824 745 );
33d40b9f 746
1f1318b8 747 // Full content
9a705a5a 748
1f1318b8 749 return newInputStreamResponse(NanoHTTPD.MIME_HTML, //
a1ddc982 750 templates.index(false, (paragraph > 0), Arrays.asList( //
1f1318b8
NR
751 navbar, //
752 viewerItem, //
753 optionbar //
754 )).read());
33d40b9f
NR
755 } catch (IOException e) {
756 Instance.getInstance().getTraceHandler()
757 .error(new IOException("Cannot get image: " + uri, e));
758 return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR,
759 NanoHTTPD.MIME_PLAINTEXT, "Error when processing request");
760 }
761 }
762
763 protected Response newInputStreamResponse(String mimeType, InputStream in) {
764 if (in == null) {
765 return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "",
766 null);
767 }
768 return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in);
769 }
33d40b9f 770}