check updates fix
[nikiroo-utils.git] / Main.java
1 package be.nikiroo.fanfix;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.MalformedURLException;
6 import java.net.URL;
7 import java.util.ArrayList;
8 import java.util.List;
9
10 import javax.net.ssl.SSLException;
11
12 import be.nikiroo.fanfix.bundles.Config;
13 import be.nikiroo.fanfix.bundles.StringId;
14 import be.nikiroo.fanfix.data.Chapter;
15 import be.nikiroo.fanfix.data.MetaData;
16 import be.nikiroo.fanfix.data.Story;
17 import be.nikiroo.fanfix.library.BasicLibrary;
18 import be.nikiroo.fanfix.library.CacheLibrary;
19 import be.nikiroo.fanfix.library.LocalLibrary;
20 import be.nikiroo.fanfix.library.RemoteLibrary;
21 import be.nikiroo.fanfix.library.RemoteLibraryServer;
22 import be.nikiroo.fanfix.output.BasicOutput;
23 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
24 import be.nikiroo.fanfix.reader.BasicReader;
25 import be.nikiroo.fanfix.reader.CliReader;
26 import be.nikiroo.fanfix.searchable.BasicSearchable;
27 import be.nikiroo.fanfix.supported.BasicSupport;
28 import be.nikiroo.fanfix.supported.SupportType;
29 import be.nikiroo.utils.Progress;
30 import be.nikiroo.utils.Version;
31 import be.nikiroo.utils.serial.server.ServerObject;
32
33 /**
34 * Main program entry point.
35 *
36 * @author niki
37 */
38 public class Main {
39 private enum MainAction {
40 IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG
41 }
42
43 /**
44 * Main program entry point.
45 * <p>
46 * Known environment variables:
47 * <ul>
48 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
49 * {@link String}s when possible</li>
50 * <li>CONFIG_DIR: a path where to look for the <tt>.properties</tt> files
51 * before taking the usual ones; they will also be saved/updated into this
52 * path when the program starts</li>
53 * <li>DEBUG: if set to 1 or 'true', the program will override the DEBUG_ERR
54 * configuration value with 'true'</li>
55 * </ul>
56 * <p>
57 * <ul>
58 * <li>--import [URL]: import into library</li>
59 * <li>--export [id] [output_type] [target]: export story to target</li>
60 * <li>--convert [URL] [output_type] [target] (+info): convert URL into
61 * target</li>
62 * <li>--read [id] ([chapter number]): read the given story from the library
63 * </li>
64 * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
65 * story, without saving it</li>
66 * <li>--search: list the supported websites (where)</li>
67 * <li>--search [where] [keywords] (page [page]) (item [item]): search on
68 * the supported website and display the given results page of stories it
69 * found, or the story details if asked</li>
70 * <li>--search-tag [where]: list all the tags supported by this website</li>
71 * <li>--search-tag [index 1]... (page [page]) (item [item]): search for the
72 * given stories or subtags, tag by tag, and display information about a
73 * specific page of results or about a specific item if requested</li>
74 * <li>--list ([type]): list the stories present in the library</li>
75 * <li>--set-source [id] [new source]: change the source of the given story</li>
76 * <li>--set-title [id] [new title]: change the title of the given story</li>
77 * <li>--set-author [id] [new author]: change the author of the given story</li>
78 * <li>--version: get the version of the program</li>
79 * <li>--server: start the server mode (see config file for parameters)</li>
80 * <li>--stop-server: stop the running server on this port if any</li>
81 * <li>--remote [key] [host] [port]: use a the given remote library</li>
82 * </ul>
83 *
84 * @param args
85 * see method description
86 */
87 public static void main(String[] args) {
88 new Main().start(args);
89 }
90
91 /**
92 * Start the default handling for the application.
93 * <p>
94 * If specific actions were asked (with correct parameters), they will be
95 * forwarded to the different protected methods that you can override.
96 * <p>
97 * At the end of the method, {@link Main#exit(int)} will be called; by
98 * default, it calls {@link System#exit(int)} if the status is not 0.
99 *
100 * @param args
101 * the arguments received from the system
102 */
103 public void start(String [] args) {
104 // Only one line, but very important:
105 Instance.init();
106
107 String urlString = null;
108 String luid = null;
109 String sourceString = null;
110 String titleString = null;
111 String authorString = null;
112 String chapString = null;
113 String target = null;
114 String key = null;
115 MainAction action = MainAction.START;
116 Boolean plusInfo = null;
117 String host = null;
118 Integer port = null;
119 SupportType searchOn = null;
120 String search = null;
121 List<Integer> tags = new ArrayList<Integer>();
122 Integer page = null;
123 Integer item = null;
124
125 boolean noMoreActions = false;
126
127 int exitCode = 0;
128 for (int i = 0; exitCode == 0 && i < args.length; i++) {
129 // Action (--) handling:
130 if (!noMoreActions && args[i].startsWith("--")) {
131 if (args[i].equals("--")) {
132 noMoreActions = true;
133 } else {
134 try {
135 action = MainAction.valueOf(args[i].substring(2)
136 .toUpperCase().replace("-", "_"));
137 } catch (Exception e) {
138 Instance.getInstance().getTraceHandler()
139 .error(new IllegalArgumentException("Unknown action: " + args[i], e));
140 exitCode = 255;
141 }
142 }
143
144 continue;
145 }
146
147 switch (action) {
148 case IMPORT:
149 if (urlString == null) {
150 urlString = args[i];
151 } else {
152 exitCode = 255;
153 }
154 break;
155 case EXPORT:
156 if (luid == null) {
157 luid = args[i];
158 } else if (sourceString == null) {
159 sourceString = args[i];
160 } else if (target == null) {
161 target = args[i];
162 } else {
163 exitCode = 255;
164 }
165 break;
166 case CONVERT:
167 if (urlString == null) {
168 urlString = args[i];
169 } else if (sourceString == null) {
170 sourceString = args[i];
171 } else if (target == null) {
172 target = args[i];
173 } else if (plusInfo == null) {
174 if ("+info".equals(args[i])) {
175 plusInfo = true;
176 } else {
177 exitCode = 255;
178 }
179 } else {
180 exitCode = 255;
181 }
182 break;
183 case LIST:
184 if (sourceString == null) {
185 sourceString = args[i];
186 } else {
187 exitCode = 255;
188 }
189 break;
190 case SET_SOURCE:
191 if (luid == null) {
192 luid = args[i];
193 } else if (sourceString == null) {
194 sourceString = args[i];
195 } else {
196 exitCode = 255;
197 }
198 break;
199 case SET_TITLE:
200 if (luid == null) {
201 luid = args[i];
202 } else if (sourceString == null) {
203 titleString = args[i];
204 } else {
205 exitCode = 255;
206 }
207 break;
208 case SET_AUTHOR:
209 if (luid == null) {
210 luid = args[i];
211 } else if (sourceString == null) {
212 authorString = args[i];
213 } else {
214 exitCode = 255;
215 }
216 break;
217 case READ:
218 if (luid == null) {
219 luid = args[i];
220 } else if (chapString == null) {
221 chapString = args[i];
222 } else {
223 exitCode = 255;
224 }
225 break;
226 case READ_URL:
227 if (urlString == null) {
228 urlString = args[i];
229 } else if (chapString == null) {
230 chapString = args[i];
231 } else {
232 exitCode = 255;
233 }
234 break;
235 case SEARCH:
236 if (searchOn == null) {
237 searchOn = SupportType.valueOfAllOkUC(args[i]);
238
239 if (searchOn == null) {
240 Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">");
241 exitCode = 41;
242 break;
243 }
244
245 if (BasicSearchable.getSearchable(searchOn) == null) {
246 Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn);
247 exitCode = 42;
248 break;
249 }
250 } else if (search == null) {
251 search = args[i];
252 } else if (page != null && page == -1) {
253 try {
254 page = Integer.parseInt(args[i]);
255 } catch (Exception e) {
256 page = -2;
257 }
258 } else if (item != null && item == -1) {
259 try {
260 item = Integer.parseInt(args[i]);
261 } catch (Exception e) {
262 item = -2;
263 }
264 } else if (page == null || item == null) {
265 if (page == null && "page".equals(args[i])) {
266 page = -1;
267 } else if (item == null && "item".equals(args[i])) {
268 item = -1;
269 } else {
270 exitCode = 255;
271 }
272 } else {
273 exitCode = 255;
274 }
275 break;
276 case SEARCH_TAG:
277 if (searchOn == null) {
278 searchOn = SupportType.valueOfAllOkUC(args[i]);
279
280 if (searchOn == null) {
281 Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">");
282 exitCode = 255;
283 }
284
285 if (BasicSearchable.getSearchable(searchOn) == null) {
286 Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn);
287 exitCode = 255;
288 }
289 } else if (page == null && item == null) {
290 if ("page".equals(args[i])) {
291 page = -1;
292 } else if ("item".equals(args[i])) {
293 item = -1;
294 } else {
295 try {
296 int index = Integer.parseInt(args[i]);
297 tags.add(index);
298 } catch (NumberFormatException e) {
299 Instance.getInstance().getTraceHandler().error("Invalid tag index: " + args[i]);
300 exitCode = 255;
301 }
302 }
303 } else if (page != null && page == -1) {
304 try {
305 page = Integer.parseInt(args[i]);
306 } catch (Exception e) {
307 page = -2;
308 }
309 } else if (item != null && item == -1) {
310 try {
311 item = Integer.parseInt(args[i]);
312 } catch (Exception e) {
313 item = -2;
314 }
315 } else if (page == null || item == null) {
316 if (page == null && "page".equals(args[i])) {
317 page = -1;
318 } else if (item == null && "item".equals(args[i])) {
319 item = -1;
320 } else {
321 exitCode = 255;
322 }
323 } else {
324 exitCode = 255;
325 }
326 break;
327 case HELP:
328 exitCode = 255;
329 break;
330 case START:
331 exitCode = 255; // not supposed to be selected by user
332 break;
333 case VERSION:
334 exitCode = 255; // no arguments for this option
335 break;
336 case SERVER:
337 exitCode = 255; // no arguments for this option
338 break;
339 case STOP_SERVER:
340 exitCode = 255; // no arguments for this option
341 break;
342 case REMOTE:
343 if (key == null) {
344 key = args[i];
345 } else if (host == null) {
346 host = args[i];
347 } else if (port == null) {
348 port = Integer.parseInt(args[i]);
349
350 BasicLibrary lib = new RemoteLibrary(key, host, port);
351 lib = new CacheLibrary(
352 Instance.getInstance().getRemoteDir(host), lib,
353 Instance.getInstance().getUiConfig());
354
355 Instance.getInstance().setLibrary(lib);
356
357 action = MainAction.START;
358 } else {
359 exitCode = 255;
360 }
361 break;
362 }
363 }
364
365 final Progress mainProgress = new Progress(0, 80);
366 mainProgress.addProgressListener(new Progress.ProgressListener() {
367 private int current = mainProgress.getMin();
368
369 @Override
370 public void progress(Progress progress, String name) {
371 int diff = progress.getProgress() - current;
372 current += diff;
373
374 if (diff <= 0)
375 return;
376
377 StringBuilder builder = new StringBuilder();
378 for (int i = 0; i < diff; i++) {
379 builder.append('.');
380 }
381
382 System.err.print(builder.toString());
383
384 if (progress.isDone()) {
385 System.err.println("");
386 }
387 }
388 });
389 Progress pg = new Progress();
390 mainProgress.addProgress(pg, mainProgress.getMax());
391
392 VersionCheck updates = checkUpdates();
393
394 if (exitCode == 0) {
395 switch (action) {
396 case IMPORT:
397 if (updates != null)
398 updates.ok(); // we consider it read
399
400 try {
401 exitCode = imprt(BasicReader.getUrl(urlString), pg);
402 } catch (MalformedURLException e) {
403 Instance.getInstance().getTraceHandler().error(e);
404 exitCode = 1;
405 }
406
407 break;
408 case EXPORT:
409 if (updates != null)
410 updates.ok(); // we consider it read
411
412 OutputType exportType = OutputType.valueOfNullOkUC(sourceString, null);
413 if (exportType == null) {
414 Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, sourceString)));
415 exitCode = 1;
416 break;
417 }
418
419 exitCode = export(luid, exportType, target, pg);
420
421 break;
422 case CONVERT:
423 if (updates != null)
424 updates.ok(); // we consider it read
425
426 OutputType convertType = OutputType.valueOfAllOkUC(sourceString, null);
427 if (convertType == null) {
428 Instance.getInstance().getTraceHandler()
429 .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, sourceString)));
430
431 exitCode = 2;
432 break;
433 }
434
435 exitCode = convert(urlString, convertType, target,
436 plusInfo == null ? false : plusInfo, pg);
437
438 break;
439 case LIST:
440 exitCode = list(sourceString);
441 break;
442 case SET_SOURCE:
443 try {
444 Instance.getInstance().getLibrary().changeSource(luid, sourceString, pg);
445 } catch (IOException e1) {
446 Instance.getInstance().getTraceHandler().error(e1);
447 exitCode = 21;
448 }
449 break;
450 case SET_TITLE:
451 try {
452 Instance.getInstance().getLibrary().changeTitle(luid, titleString, pg);
453 } catch (IOException e1) {
454 Instance.getInstance().getTraceHandler().error(e1);
455 exitCode = 22;
456 }
457 break;
458 case SET_AUTHOR:
459 try {
460 Instance.getInstance().getLibrary().changeAuthor(luid, authorString, pg);
461 } catch (IOException e1) {
462 Instance.getInstance().getTraceHandler().error(e1);
463 exitCode = 23;
464 }
465 break;
466 case READ:
467 if (luid == null || luid.isEmpty()) {
468 syntax(false);
469 exitCode = 255;
470 break;
471 }
472
473 try {
474 Integer chap = null;
475 if (chapString != null) {
476 try {
477 chap = Integer.parseInt(chapString);
478 } catch (NumberFormatException e) {
479 Instance.getInstance().getTraceHandler().error(new IOException(
480 "Chapter number cannot be parsed: " + chapString, e));
481 exitCode = 2;
482 break;
483 }
484 }
485
486 BasicLibrary lib = Instance.getInstance().getLibrary();
487 exitCode = read(lib.getStory(luid, null), chap);
488 } catch (IOException e) {
489 Instance.getInstance().getTraceHandler()
490 .error(new IOException("Failed to read book", e));
491 exitCode = 2;
492 }
493
494 break;
495 case READ_URL:
496 if (urlString == null || urlString.isEmpty()) {
497 syntax(false);
498 exitCode = 255;
499 break;
500 }
501
502 try {
503 Integer chap = null;
504 if (chapString != null) {
505 try {
506 chap = Integer.parseInt(chapString);
507 } catch (NumberFormatException e) {
508 Instance.getInstance().getTraceHandler().error(new IOException(
509 "Chapter number cannot be parsed: " + chapString, e));
510 exitCode = 2;
511 break;
512 }
513 }
514
515 BasicSupport support = BasicSupport
516 .getSupport(BasicReader.getUrl(urlString));
517 if (support == null) {
518 Instance.getInstance().getTraceHandler()
519 .error("URL not supported: " + urlString);
520 exitCode = 2;
521 break;
522 }
523
524 exitCode = read(support.process(null), chap);
525 } catch (IOException e) {
526 Instance.getInstance().getTraceHandler()
527 .error(new IOException("Failed to read book", e));
528 exitCode = 2;
529 }
530
531 break;
532 case SEARCH:
533 page = page == null ? 1 : page;
534 if (page < 0) {
535 Instance.getInstance().getTraceHandler().error("Incorrect page number");
536 exitCode = 255;
537 break;
538 }
539
540 item = item == null ? 0 : item;
541 if (item < 0) {
542 Instance.getInstance().getTraceHandler().error("Incorrect item number");
543 exitCode = 255;
544 break;
545 }
546
547 if (searchOn == null) {
548 try {
549 search();
550 } catch (IOException e) {
551 Instance.getInstance().getTraceHandler().error(e);
552 exitCode = 1;
553 }
554 } else if (search != null) {
555 try {
556 searchKeywords(searchOn, search, page, item);
557 } catch (IOException e) {
558 Instance.getInstance().getTraceHandler().error(e);
559 exitCode = 20;
560 }
561 } else {
562 exitCode = 255;
563 }
564
565 break;
566 case SEARCH_TAG:
567 if (searchOn == null) {
568 exitCode = 255;
569 break;
570 }
571
572 page = page == null ? 1 : page;
573 if (page < 0) {
574 Instance.getInstance().getTraceHandler().error("Incorrect page number");
575 exitCode = 255;
576 break;
577 }
578
579 item = item == null ? 0 : item;
580 if (item < 0) {
581 Instance.getInstance().getTraceHandler().error("Incorrect item number");
582 exitCode = 255;
583 break;
584 }
585
586 try {
587 searchTags(searchOn, page, item,
588 tags.toArray(new Integer[] {}));
589 } catch (IOException e) {
590 Instance.getInstance().getTraceHandler().error(e);
591 }
592
593 break;
594 case HELP:
595 syntax(true);
596 exitCode = 0;
597 break;
598 case VERSION:
599 if (updates != null)
600 updates.ok(); // we consider it read
601
602 System.out
603 .println(String.format("Fanfix version %s"
604 + "%nhttps://github.com/nikiroo/fanfix/"
605 + "%n\tWritten by Nikiroo",
606 Version.getCurrentVersion()));
607 break;
608 case START:
609 try {
610 start();
611 } catch (IOException e) {
612 Instance.getInstance().getTraceHandler().error(e);
613 exitCode = 66;
614 }
615 break;
616 case SERVER:
617 key = Instance.getInstance().getConfig().getString(Config.SERVER_KEY);
618 port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT);
619 if (port == null) {
620 System.err.println("No port configured in the config file");
621 exitCode = 15;
622 break;
623 }
624 try {
625 startServer(key, port);
626 } catch (IOException e) {
627 Instance.getInstance().getTraceHandler().error(e);
628 }
629
630 break;
631 case STOP_SERVER:
632 // Can be given via "--remote XX XX XX"
633 if (key == null)
634 key = Instance.getInstance().getConfig().getString(Config.SERVER_KEY);
635 if (port == null)
636 port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT);
637
638 if (port == null) {
639 System.err.println("No port given nor configured in the config file");
640 exitCode = 15;
641 break;
642 }
643 try {
644 stopServer(key, host, port);
645 } catch (SSLException e) {
646 Instance.getInstance().getTraceHandler().error(
647 "Bad access key for remote library");
648 exitCode = 43;
649 } catch (IOException e) {
650 Instance.getInstance().getTraceHandler().error(e);
651 exitCode = 44;
652 }
653
654 break;
655 case REMOTE:
656 exitCode = 255; // should not be reachable (REMOTE -> START)
657 break;
658 }
659 }
660
661 try {
662 Instance.getInstance().getTempFiles().close();
663 } catch (IOException e) {
664 Instance.getInstance().getTraceHandler().error(new IOException(
665 "Cannot dispose of the temporary files", e));
666 }
667
668 if (exitCode == 255) {
669 syntax(false);
670 }
671
672 exit(exitCode);
673 }
674
675 /**
676 * A normal invocation of the program (without parameters or at least
677 * without "action" parameters).
678 * <p>
679 * You will probably want to override that one if you offer a user
680 * interface.
681 *
682 * @throws IOException
683 * in case of I/O error
684 */
685 protected void start() throws IOException {
686 new CliReader().listBooks(null);
687 }
688
689 /**
690 * Will check if updates are available, synchronously.
691 * <p>
692 * For this, it will simply forward the call to
693 * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix".
694 * <p>
695 * You may want to override it so you call the forward method with the right
696 * parameters (or also if you want it to be asynchronous).
697 *
698 * @return the newer version information or NULL if nothing new
699 */
700 protected VersionCheck checkUpdates() {
701 return checkUpdates("nikiroo/fanfix");
702 }
703
704 /**
705 * Will check if updates are available on a specific GitHub project.
706 * <p>
707 * Will be called by {@link Main#checkUpdates()}, but if you override that
708 * one you mall call it with another project.
709 *
710 * @param githubProject
711 * the GitHub project, for instance "nikiroo/fanfix"
712 *
713 * @return the newer version information or NULL if nothing new
714 */
715 protected VersionCheck checkUpdates(String githubProject) {
716 VersionCheck updates = VersionCheck.check(githubProject);
717 if (updates.isNewVersionAvailable()) {
718 notifyUpdates(updates);
719 return updates;
720 }
721
722 return null;
723 }
724
725 /**
726 * Notify the user about available updates.
727 * <p>
728 * Will only be called when a version is available.
729 * <p>
730 * Note that you can call {@link VersionCheck#ok()} on it if the user has
731 * read the information (by default, it is marked read only on certain other
732 * actions).
733 *
734 * @param updates
735 * the new version information
736 */
737 protected void notifyUpdates(VersionCheck updates) {
738 // Sent to syserr so not to cause problem if one tries to capture a
739 // story content in text mode
740 System.err.println(
741 "A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
742 System.err.println("");
743 for (Version v : updates.getNewer()) {
744 System.err.println("\tVersion " + v);
745 System.err.println("\t-------------");
746 System.err.println("");
747 for (String it : updates.getChanges().get(v)) {
748 System.err.println("\t- " + it);
749 }
750 System.err.println("");
751 }
752 }
753
754 /**
755 * Import the given resource into the {@link LocalLibrary}.
756 *
757 * @param url
758 * the resource to import
759 * @param pg
760 * the optional progress reporter
761 *
762 * @return the exit return code (0 = success)
763 */
764 protected static int imprt(URL url, Progress pg) {
765 try {
766 MetaData meta = Instance.getInstance().getLibrary().imprt(url, pg);
767 System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported.");
768 } catch (IOException e) {
769 Instance.getInstance().getTraceHandler().error(e);
770 return 1;
771 }
772
773 return 0;
774 }
775
776 /**
777 * Export the {@link Story} from the {@link LocalLibrary} to the given
778 * target.
779 *
780 * @param luid
781 * the story LUID
782 * @param type
783 * the {@link OutputType} to use
784 * @param target
785 * the target
786 * @param pg
787 * the optional progress reporter
788 *
789 * @return the exit return code (0 = success)
790 */
791 protected static int export(String luid, OutputType type, String target,
792 Progress pg) {
793 try {
794 Instance.getInstance().getLibrary().export(luid, type, target, pg);
795 } catch (IOException e) {
796 Instance.getInstance().getTraceHandler().error(e);
797 return 4;
798 }
799
800 return 0;
801 }
802
803 /**
804 * List the stories of the given source from the {@link LocalLibrary}
805 * (unless NULL is passed, in which case all stories will be listed).
806 *
807 * @param source
808 * the source to list the known stories of, or NULL to list all
809 * stories
810 *
811 * @return the exit return code (0 = success)
812 */
813 protected int list(String source) {
814 try {
815 new CliReader().listBooks(source);
816 } catch (IOException e) {
817 Instance.getInstance().getTraceHandler().error(e);
818 return 66;
819 }
820
821 return 0;
822 }
823
824 /**
825 * Start the current reader for this {@link Story}.
826 *
827 * @param story
828 * the story to read
829 * @param chap
830 * which {@link Chapter} to read (starting at 1), or NULL to get
831 * the {@link Story} description
832 *
833 * @return the exit return code (0 = success)
834 */
835 protected int read(Story story, Integer chap) {
836 if (story != null) {
837 try {
838 if (chap == null) {
839 new CliReader().listChapters(story);
840 } else {
841 new CliReader().printChapter(story, chap);
842 }
843 } catch (IOException e) {
844 Instance.getInstance().getTraceHandler()
845 .error(new IOException("Failed to read book", e));
846 return 2;
847 }
848 } else {
849 Instance.getInstance().getTraceHandler()
850 .error("Cannot find book: " + story);
851 return 2;
852 }
853
854 return 0;
855 }
856
857 /**
858 * Convert the {@link Story} into another format.
859 *
860 * @param urlString
861 * the source {@link Story} to convert
862 * @param type
863 * the {@link OutputType} to convert to
864 * @param target
865 * the target file
866 * @param infoCover
867 * TRUE to also export the cover and info file, even if the given
868 * {@link OutputType} does not usually save them
869 * @param pg
870 * the optional progress reporter
871 *
872 * @return the exit return code (0 = success)
873 */
874 protected int convert(String urlString, OutputType type,
875 String target, boolean infoCover, Progress pg) {
876 int exitCode = 0;
877
878 Instance.getInstance().getTraceHandler().trace("Convert: " + urlString);
879 String sourceName = urlString;
880 try {
881 URL source = BasicReader.getUrl(urlString);
882 sourceName = source.toString();
883 if (sourceName.startsWith("file://")) {
884 sourceName = sourceName.substring("file://".length());
885 }
886
887 try {
888 BasicSupport support = BasicSupport.getSupport(source);
889
890 if (support != null) {
891 Instance.getInstance().getTraceHandler()
892 .trace("Support found: " + support.getClass());
893 Progress pgIn = new Progress();
894 Progress pgOut = new Progress();
895 if (pg != null) {
896 pg.setMax(2);
897 pg.addProgress(pgIn, 1);
898 pg.addProgress(pgOut, 1);
899 }
900
901 Story story = support.process(pgIn);
902 try {
903 target = new File(target).getAbsolutePath();
904 BasicOutput.getOutput(type, infoCover, infoCover)
905 .process(story, target, pgOut);
906 } catch (IOException e) {
907 Instance.getInstance().getTraceHandler()
908 .error(new IOException(
909 trans(StringId.ERR_SAVING, target), e));
910 exitCode = 5;
911 }
912 } else {
913 Instance.getInstance().getTraceHandler()
914 .error(new IOException(
915 trans(StringId.ERR_NOT_SUPPORTED, source)));
916
917 exitCode = 4;
918 }
919 } catch (IOException e) {
920 Instance.getInstance().getTraceHandler().error(new IOException(
921 trans(StringId.ERR_LOADING, sourceName), e));
922 exitCode = 3;
923 }
924 } catch (MalformedURLException e) {
925 Instance.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e));
926 exitCode = 1;
927 }
928
929 return exitCode;
930 }
931
932 /**
933 * Display the correct syntax of the program to the user to stdout, or an
934 * error message if the syntax used was wrong on stderr.
935 *
936 * @param showHelp
937 * TRUE to show the syntax help, FALSE to show "syntax error"
938 */
939 protected void syntax(boolean showHelp) {
940 if (showHelp) {
941 StringBuilder builder = new StringBuilder();
942 for (SupportType type : SupportType.values()) {
943 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
944 type.getDesc()));
945 builder.append('\n');
946 }
947
948 String typesIn = builder.toString();
949 builder.setLength(0);
950
951 for (OutputType type : OutputType.values()) {
952 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
953 type.getDesc(true)));
954 builder.append('\n');
955 }
956
957 String typesOut = builder.toString();
958
959 System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut));
960 } else {
961 System.err.println(trans(StringId.ERR_SYNTAX));
962 }
963 }
964
965 /**
966 * Starts a search operation (i.e., list the available web sites we can
967 * search on).
968 *
969 * @throws IOException
970 * in case of I/O errors
971 */
972 protected void search() throws IOException {
973 new CliReader().listSearchables();
974 }
975
976 /**
977 * Search for books by keywords on the given supported web site.
978 *
979 * @param searchOn
980 * the web site to search on
981 * @param search
982 * the keyword to look for
983 * @param page
984 * the page of results to get, or 0 to inquire about the number
985 * of pages
986 * @param item
987 * the index of the book we are interested by, or 0 to query
988 * about how many books are in that page of results
989 *
990 * @throws IOException
991 * in case of I/O error
992 */
993 protected void searchKeywords(SupportType searchOn, String search,
994 int page, Integer item) throws IOException {
995 new CliReader().searchBooksByKeyword(searchOn, search, page, item);
996 }
997
998 /**
999 * Search for books by tags on the given supported web site.
1000 *
1001 * @param searchOn
1002 * the web site to search on
1003 * @param page
1004 * the page of results to get, or 0 to inquire about the number
1005 * of pages
1006 * @param item
1007 * the index of the book we are interested by, or 0 to query
1008 * about how many books are in that page of results
1009 * @param tags
1010 * the tags to look for
1011 *
1012 * @throws IOException
1013 * in case of I/O error
1014 */
1015 protected void searchTags(SupportType searchOn, Integer page, Integer item,
1016 Integer[] tags) throws IOException {
1017 new CliReader().searchBooksByTag(searchOn, page, item, tags);
1018 }
1019
1020 /**
1021 * Start a Fanfix server.
1022 *
1023 * @param key
1024 * the key taht will be needed to contact the Fanfix server
1025 * @param port
1026 * the port on which to run
1027 *
1028 * @throws IOException
1029 * in case of I/O errors
1030 * @throws SSLException
1031 * when the key was not accepted
1032 */
1033 private void startServer(String key, int port) throws IOException {
1034 ServerObject server = new RemoteLibraryServer(key, port);
1035 server.setTraceHandler(Instance.getInstance().getTraceHandler());
1036 server.run();
1037 }
1038
1039 /**
1040 * Stop a running Fanfix server.
1041 *
1042 * @param key
1043 * the key to contact the Fanfix server
1044 * @param host
1045 * the host on which it runs (NULL means localhost)
1046 * @param port
1047 * the port on which it runs
1048 *
1049 * @throws IOException
1050 * in case of I/O errors
1051 * @throws SSLException
1052 * when the key was not accepted
1053 */
1054 private void stopServer(
1055 String key, String host, Integer port)
1056 throws IOException, SSLException {
1057 new RemoteLibrary(key, host, port).exit();
1058 }
1059
1060 /**
1061 * We are done and ready to exit.
1062 * <p>
1063 * By default, it will call {@link System#exit(int)} if the status is not 0.
1064 *
1065 * @param status
1066 * the exit status
1067 */
1068 protected void exit(int status) {
1069 if (status != 0) {
1070 System.exit(status);
1071 }
1072 }
1073
1074 /**
1075 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
1076 *
1077 * @param id
1078 * the ID to translate
1079 *
1080 * @return the translated result
1081 */
1082 static private String trans(StringId id, Object... params) {
1083 return Instance.getInstance().getTrans().getString(id, params);
1084 }
1085 }