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