manage remote and io exception in fanfix
[nikiroo-utils.git] / src / be / nikiroo / fanfix / 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;
15import be.nikiroo.fanfix.data.Story;
ff05b828
NR
16import be.nikiroo.fanfix.library.BasicLibrary;
17import be.nikiroo.fanfix.library.CacheLibrary;
e42573a0
NR
18import be.nikiroo.fanfix.library.LocalLibrary;
19import be.nikiroo.fanfix.library.RemoteLibrary;
20import be.nikiroo.fanfix.library.RemoteLibraryServer;
08fe2e33
NR
21import be.nikiroo.fanfix.output.BasicOutput;
22import be.nikiroo.fanfix.output.BasicOutput.OutputType;
3727aae2 23import be.nikiroo.fanfix.reader.BasicReader;
e42573a0
NR
24import be.nikiroo.fanfix.reader.Reader;
25import be.nikiroo.fanfix.reader.Reader.ReaderType;
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 {
8141d7ac 40 IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, 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>
c1873e56
NR
78 * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
79 * for this command</li>
39c3c689 80 * <li>--version: get the version of the program</li>
fb25273c
NR
81 * <li>--server: start the server mode (see config file for parameters)</li>
82 * <li>--stop-server: stop the running server on this port if any</li>
2070ced5 83 * <li>--remote [key] [host] [port]: use a the given remote library</li>
08fe2e33
NR
84 * </ul>
85 *
86 * @param args
d0114000 87 * see method description
08fe2e33
NR
88 */
89 public static void main(String[] args) {
d0114000
NR
90 String urlString = null;
91 String luid = null;
b0e88ebd 92 String sourceString = null;
e10b51a2
NR
93 String titleString = null;
94 String authorString = null;
d0114000
NR
95 String chapString = null;
96 String target = null;
2070ced5 97 String key = null;
333f0e7b 98 MainAction action = MainAction.START;
d0114000 99 Boolean plusInfo = null;
b0e88ebd
NR
100 String host = null;
101 Integer port = null;
91b82a5c
NR
102 SupportType searchOn = null;
103 String search = null;
8b153400 104 List<Integer> tags = new ArrayList<Integer>();
91b82a5c
NR
105 Integer page = null;
106 Integer item = null;
73ce17ef 107
d0114000
NR
108 boolean noMoreActions = false;
109
110 int exitCode = 0;
111 for (int i = 0; exitCode == 0 && i < args.length; i++) {
112 // Action (--) handling:
113 if (!noMoreActions && args[i].startsWith("--")) {
114 if (args[i].equals("--")) {
115 noMoreActions = true;
116 } else {
117 try {
118 action = MainAction.valueOf(args[i].substring(2)
119 .toUpperCase().replace("-", "_"));
120 } catch (Exception e) {
62c63b07
NR
121 Instance.getTraceHandler().error(
122 new IllegalArgumentException("Unknown action: "
123 + args[i], e));
d0114000
NR
124 exitCode = 255;
125 }
126 }
08fe2e33 127
d0114000
NR
128 continue;
129 }
130
131 switch (action) {
132 case IMPORT:
133 if (urlString == null) {
134 urlString = args[i];
135 } else {
136 exitCode = 255;
137 }
138 break;
139 case EXPORT:
140 if (luid == null) {
141 luid = args[i];
b0e88ebd
NR
142 } else if (sourceString == null) {
143 sourceString = args[i];
d0114000
NR
144 } else if (target == null) {
145 target = args[i];
146 } else {
147 exitCode = 255;
148 }
149 break;
150 case CONVERT:
151 if (urlString == null) {
152 urlString = args[i];
b0e88ebd
NR
153 } else if (sourceString == null) {
154 sourceString = args[i];
d0114000
NR
155 } else if (target == null) {
156 target = args[i];
157 } else if (plusInfo == null) {
158 if ("+info".equals(args[i])) {
159 plusInfo = true;
160 } else {
161 exitCode = 255;
162 }
163 } else {
164 exitCode = 255;
08fe2e33 165 }
d0114000
NR
166 break;
167 case LIST:
b0e88ebd
NR
168 if (sourceString == null) {
169 sourceString = args[i];
d0114000
NR
170 } else {
171 exitCode = 255;
08fe2e33 172 }
d0114000 173 break;
e10b51a2
NR
174 case SET_SOURCE:
175 if (luid == null) {
176 luid = args[i];
177 } else if (sourceString == null) {
178 sourceString = args[i];
179 } else {
180 exitCode = 255;
181 }
182 break;
183 case SET_TITLE:
184 if (luid == null) {
185 luid = args[i];
186 } else if (sourceString == null) {
187 titleString = args[i];
188 } else {
189 exitCode = 255;
190 }
191 break;
192 case SET_AUTHOR:
193 if (luid == null) {
194 luid = args[i];
195 } else if (sourceString == null) {
196 authorString = args[i];
197 } else {
198 exitCode = 255;
199 }
200 break;
d0114000
NR
201 case READ:
202 if (luid == null) {
203 luid = args[i];
204 } else if (chapString == null) {
205 chapString = args[i];
206 } else {
207 exitCode = 255;
08fe2e33 208 }
d0114000
NR
209 break;
210 case READ_URL:
211 if (urlString == null) {
212 urlString = args[i];
213 } else if (chapString == null) {
214 chapString = args[i];
215 } else {
216 exitCode = 255;
08fe2e33 217 }
d0114000 218 break;
91b82a5c
NR
219 case SEARCH:
220 if (searchOn == null) {
221 searchOn = SupportType.valueOfAllOkUC(args[i]);
8b153400 222
91b82a5c
NR
223 if (searchOn == null) {
224 Instance.getTraceHandler().error(
225 "Website not known: <" + args[i] + ">");
b31a0db0
NR
226 exitCode = 41;
227 break;
91b82a5c 228 }
8b153400 229
91b82a5c
NR
230 if (BasicSearchable.getSearchable(searchOn) == null) {
231 Instance.getTraceHandler().error(
232 "Website not supported: " + searchOn);
b31a0db0
NR
233 exitCode = 42;
234 break;
91b82a5c
NR
235 }
236 } else if (search == null) {
237 search = args[i];
8b153400 238 } else if (page != null && page == -1) {
91b82a5c
NR
239 try {
240 page = Integer.parseInt(args[i]);
8b153400
NR
241 } catch (Exception e) {
242 page = -2;
91b82a5c 243 }
8b153400 244 } else if (item != null && item == -1) {
91b82a5c
NR
245 try {
246 item = Integer.parseInt(args[i]);
8b153400
NR
247 } catch (Exception e) {
248 item = -2;
249 }
250 } else if (page == null || item == null) {
251 if (page == null && "page".equals(args[i])) {
252 page = -1;
253 } else if (item == null && "item".equals(args[i])) {
254 item = -1;
255 } else {
91b82a5c
NR
256 exitCode = 255;
257 }
258 } else {
259 exitCode = 255;
260 }
261 break;
8141d7ac 262 case SEARCH_TAG:
91b82a5c
NR
263 if (searchOn == null) {
264 searchOn = SupportType.valueOfAllOkUC(args[i]);
8b153400 265
91b82a5c
NR
266 if (searchOn == null) {
267 Instance.getTraceHandler().error(
268 "Website not known: <" + args[i] + ">");
269 exitCode = 255;
270 }
8b153400 271
91b82a5c
NR
272 if (BasicSearchable.getSearchable(searchOn) == null) {
273 Instance.getTraceHandler().error(
274 "Website not supported: " + searchOn);
275 exitCode = 255;
276 }
8b153400
NR
277 } else if (page == null && item == null) {
278 if ("page".equals(args[i])) {
279 page = -1;
280 } else if ("item".equals(args[i])) {
281 item = -1;
282 } else {
283 try {
284 int index = Integer.parseInt(args[i]);
285 tags.add(index);
286 } catch (NumberFormatException e) {
287 Instance.getTraceHandler().error(
288 "Invalid tag index: " + args[i]);
289 exitCode = 255;
290 }
291 }
292 } else if (page != null && page == -1) {
293 try {
294 page = Integer.parseInt(args[i]);
295 } catch (Exception e) {
296 page = -2;
297 }
298 } else if (item != null && item == -1) {
299 try {
300 item = Integer.parseInt(args[i]);
301 } catch (Exception e) {
302 item = -2;
303 }
304 } else if (page == null || item == null) {
305 if (page == null && "page".equals(args[i])) {
306 page = -1;
307 } else if (item == null && "item".equals(args[i])) {
308 item = -1;
309 } else {
310 exitCode = 255;
311 }
91b82a5c 312 } else {
8b153400 313 exitCode = 255;
91b82a5c
NR
314 }
315 break;
d0114000
NR
316 case HELP:
317 exitCode = 255;
318 break;
319 case SET_READER:
7de079f1 320 exitCode = setReaderType(args[i]);
c1873e56 321 action = MainAction.START;
d0114000 322 break;
333f0e7b
NR
323 case START:
324 exitCode = 255; // not supposed to be selected by user
325 break;
39c3c689
NR
326 case VERSION:
327 exitCode = 255; // no arguments for this option
b0e88ebd
NR
328 break;
329 case SERVER:
fb25273c
NR
330 exitCode = 255; // no arguments for this option
331 break;
5e848e6a 332 case STOP_SERVER:
fb25273c 333 exitCode = 255; // no arguments for this option
b0e88ebd
NR
334 break;
335 case REMOTE:
2070ced5
NR
336 if (key == null) {
337 key = args[i];
338 } else if (host == null) {
b0e88ebd
NR
339 host = args[i];
340 } else if (port == null) {
341 port = Integer.parseInt(args[i]);
ff05b828 342
2070ced5 343 BasicLibrary lib = new RemoteLibrary(key, host, port);
5895a958 344 lib = new CacheLibrary(Instance.getRemoteDir(host), lib);
ff05b828
NR
345
346 BasicReader.setDefaultLibrary(lib);
5e848e6a 347
b0e88ebd
NR
348 action = MainAction.START;
349 } else {
350 exitCode = 255;
351 }
352 break;
d0114000
NR
353 }
354 }
355
92fb0719
NR
356 final Progress mainProgress = new Progress(0, 80);
357 mainProgress.addProgressListener(new Progress.ProgressListener() {
358 private int current = mainProgress.getMin();
359
211f7ddb 360 @Override
92fb0719
NR
361 public void progress(Progress progress, String name) {
362 int diff = progress.getProgress() - current;
363 current += diff;
364
1822d603
NR
365 if (diff <= 0)
366 return;
367
92fb0719
NR
368 StringBuilder builder = new StringBuilder();
369 for (int i = 0; i < diff; i++) {
370 builder.append('.');
371 }
372
373 System.err.print(builder.toString());
374
375 if (progress.isDone()) {
376 System.err.println("");
377 }
378 }
379 });
380 Progress pg = new Progress();
381 mainProgress.addProgress(pg, mainProgress.getMax());
382
b42117f1
NR
383 VersionCheck updates = VersionCheck.check();
384 if (updates.isNewVersionAvailable()) {
385 // Sent to syserr so not to cause problem if one tries to capture a
386 // story content in text mode
387 System.err
388 .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
389 System.err.println("");
390 for (Version v : updates.getNewer()) {
391 System.err.println("\tVersion " + v);
392 System.err.println("\t-------------");
393 System.err.println("");
91b82a5c
NR
394 for (String it : updates.getChanges().get(v)) {
395 System.err.println("\t- " + it);
b42117f1
NR
396 }
397 System.err.println("");
398 }
399 }
400
b31a0db0 401 if (exitCode == 0) {
d0114000
NR
402 switch (action) {
403 case IMPORT:
92fb0719 404 exitCode = imprt(urlString, pg);
b42117f1 405 updates.ok(); // we consider it read
d0114000
NR
406 break;
407 case EXPORT:
b0e88ebd 408 exitCode = export(luid, sourceString, target, pg);
b42117f1 409 updates.ok(); // we consider it read
d0114000
NR
410 break;
411 case CONVERT:
b0e88ebd 412 exitCode = convert(urlString, sourceString, target,
92fb0719 413 plusInfo == null ? false : plusInfo, pg);
b42117f1 414 updates.ok(); // we consider it read
d0114000
NR
415 break;
416 case LIST:
99ccbdf6 417 if (BasicReader.getReader() == null) {
62c63b07
NR
418 Instance.getTraceHandler()
419 .error(new Exception(
420 "No reader type has been configured"));
99ccbdf6
NR
421 exitCode = 10;
422 break;
423 }
b0e88ebd 424 exitCode = list(sourceString);
d0114000 425 break;
e10b51a2
NR
426 case SET_SOURCE:
427 try {
428 Instance.getLibrary().changeSource(luid, sourceString, pg);
429 } catch (IOException e1) {
430 Instance.getTraceHandler().error(e1);
431 exitCode = 21;
432 }
433 break;
434 case SET_TITLE:
435 try {
436 Instance.getLibrary().changeTitle(luid, titleString, pg);
437 } catch (IOException e1) {
438 Instance.getTraceHandler().error(e1);
439 exitCode = 22;
440 }
441 break;
442 case SET_AUTHOR:
443 try {
444 Instance.getLibrary().changeAuthor(luid, authorString, pg);
445 } catch (IOException e1) {
446 Instance.getTraceHandler().error(e1);
447 exitCode = 23;
448 }
449 break;
d0114000 450 case READ:
99ccbdf6 451 if (BasicReader.getReader() == null) {
62c63b07
NR
452 Instance.getTraceHandler()
453 .error(new Exception(
454 "No reader type has been configured"));
99ccbdf6
NR
455 exitCode = 10;
456 break;
457 }
d0114000
NR
458 exitCode = read(luid, chapString, true);
459 break;
460 case READ_URL:
99ccbdf6 461 if (BasicReader.getReader() == null) {
62c63b07
NR
462 Instance.getTraceHandler()
463 .error(new Exception(
464 "No reader type has been configured"));
99ccbdf6
NR
465 exitCode = 10;
466 break;
467 }
d0114000 468 exitCode = read(urlString, chapString, false);
91b82a5c
NR
469 break;
470 case SEARCH:
8b153400
NR
471 page = page == null ? 1 : page;
472 if (page < 0) {
473 Instance.getTraceHandler().error("Incorrect page number");
91b82a5c
NR
474 exitCode = 255;
475 break;
476 }
8b153400
NR
477
478 item = item == null ? 0 : item;
479 if (item < 0) {
480 Instance.getTraceHandler().error("Incorrect item number");
481 exitCode = 255;
482 break;
91b82a5c 483 }
8b153400 484
91b82a5c
NR
485 if (BasicReader.getReader() == null) {
486 Instance.getTraceHandler()
487 .error(new Exception(
488 "No reader type has been configured"));
489 exitCode = 10;
490 break;
491 }
8b153400 492
b31a0db0
NR
493 try {
494 if (searchOn == null) {
495 BasicReader.getReader().search(true);
496 } else if (search != null) {
497
8b153400 498 BasicReader.getReader().search(searchOn, search, page,
f76de465 499 item, true);
b31a0db0
NR
500 } else {
501 exitCode = 255;
8b153400 502 }
b31a0db0
NR
503 } catch (IOException e1) {
504 Instance.getTraceHandler().error(e1);
505 exitCode = 20;
91b82a5c 506 }
8b153400 507
91b82a5c 508 break;
8141d7ac 509 case SEARCH_TAG:
91b82a5c
NR
510 if (searchOn == null) {
511 exitCode = 255;
512 break;
513 }
91b82a5c 514
8b153400
NR
515 page = page == null ? 1 : page;
516 if (page < 0) {
517 Instance.getTraceHandler().error("Incorrect page number");
518 exitCode = 255;
519 break;
91b82a5c 520 }
8b153400
NR
521
522 item = item == null ? 0 : item;
523 if (item < 0) {
524 Instance.getTraceHandler().error("Incorrect item number");
525 exitCode = 255;
526 break;
527 }
528
91b82a5c
NR
529 if (BasicReader.getReader() == null) {
530 Instance.getTraceHandler()
531 .error(new Exception(
532 "No reader type has been configured"));
533 exitCode = 10;
534 break;
535 }
8b153400 536
91b82a5c 537 try {
8b153400 538 BasicReader.getReader().searchTag(searchOn, page, item,
f76de465 539 true, tags.toArray(new Integer[] {}));
91b82a5c
NR
540 } catch (IOException e1) {
541 Instance.getTraceHandler().error(e1);
542 }
8b153400 543
d0114000
NR
544 break;
545 case HELP:
546 syntax(true);
547 exitCode = 0;
548 break;
549 case SET_READER:
b0e88ebd 550 exitCode = 255;
d0114000 551 break;
39c3c689
NR
552 case VERSION:
553 System.out
554 .println(String.format("Fanfix version %s"
9fe3f177
NR
555 + "%nhttps://github.com/nikiroo/fanfix/"
556 + "%n\tWritten by Nikiroo",
39c3c689 557 Version.getCurrentVersion()));
b42117f1 558 updates.ok(); // we consider it read
39c3c689 559 break;
333f0e7b 560 case START:
99ccbdf6 561 if (BasicReader.getReader() == null) {
62c63b07
NR
562 Instance.getTraceHandler()
563 .error(new Exception(
564 "No reader type has been configured"));
99ccbdf6
NR
565 exitCode = 10;
566 break;
567 }
0bb51c9c
NR
568 try {
569 BasicReader.getReader().browse(null);
570 } catch (IOException e) {
571 Instance.getTraceHandler().error(e);
572 exitCode = 66;
573 }
b0e88ebd
NR
574 break;
575 case SERVER:
fb25273c
NR
576 key = Instance.getConfig().getString(Config.SERVER_KEY);
577 port = Instance.getConfig().getInteger(Config.SERVER_PORT);
b0e88ebd 578 if (port == null) {
fb25273c
NR
579 System.err.println("No port configured in the config file");
580 exitCode = 15;
b0e88ebd
NR
581 break;
582 }
583 try {
62c63b07 584 ServerObject server = new RemoteLibraryServer(key, port);
22b2b942 585 server.setTraceHandler(Instance.getTraceHandler());
edf79e5e 586 server.run();
b0e88ebd 587 } catch (IOException e) {
62c63b07 588 Instance.getTraceHandler().error(e);
b0e88ebd
NR
589 }
590 return;
5e848e6a 591 case STOP_SERVER:
fb25273c
NR
592 key = Instance.getConfig().getString(Config.SERVER_KEY);
593 port = Instance.getConfig().getInteger(Config.SERVER_PORT);
5e848e6a 594 if (port == null) {
fb25273c
NR
595 System.err.println("No port configured in the config file");
596 exitCode = 15;
5e848e6a
NR
597 break;
598 }
0bb51c9c
NR
599 try {
600 new RemoteLibrary(key, host, port).exit();
601 } catch (SSLException e) {
602 Instance.getTraceHandler().error(
603 "Bad access key for remote library");
604 exitCode = 43;
605 } catch (IOException e) {
606 Instance.getTraceHandler().error(e);
607 exitCode = 44;
608 }
5e848e6a 609
5e848e6a 610 break;
b0e88ebd 611 case REMOTE:
99ccbdf6 612 exitCode = 255; // should not be reachable (REMOTE -> START)
333f0e7b 613 break;
08fe2e33
NR
614 }
615 }
616
350bc060
NR
617 try {
618 Instance.getTempFiles().close();
619 } catch (IOException e) {
620 Instance.getTraceHandler()
621 .error(new IOException(
622 "Cannot dispose of the temporary files", e));
2aac79c7
NR
623 }
624
08fe2e33 625 if (exitCode == 255) {
d0114000 626 syntax(false);
08fe2e33
NR
627 }
628
31e28683 629 System.exit(exitCode);
08fe2e33
NR
630 }
631
08fe2e33 632 /**
68e2c6d2 633 * Import the given resource into the {@link LocalLibrary}.
08fe2e33 634 *
d0114000 635 * @param urlString
08fe2e33 636 * the resource to import
92fb0719
NR
637 * @param pg
638 * the optional progress reporter
08fe2e33
NR
639 *
640 * @return the exit return code (0 = success)
641 */
92fb0719 642 public static int imprt(String urlString, Progress pg) {
08fe2e33 643 try {
3b2b638f
NR
644 Story story = Instance.getLibrary().imprt(
645 BasicReader.getUrl(urlString), pg);
08fe2e33
NR
646 System.out.println(story.getMeta().getLuid() + ": \""
647 + story.getMeta().getTitle() + "\" imported.");
648 } catch (IOException e) {
62c63b07 649 Instance.getTraceHandler().error(e);
08fe2e33
NR
650 return 1;
651 }
652
653 return 0;
654 }
655
656 /**
68e2c6d2
NR
657 * Export the {@link Story} from the {@link LocalLibrary} to the given
658 * target.
08fe2e33 659 *
73ce17ef 660 * @param luid
08fe2e33
NR
661 * the story LUID
662 * @param typeString
663 * the {@link OutputType} to use
664 * @param target
665 * the target
92fb0719
NR
666 * @param pg
667 * the optional progress reporter
08fe2e33
NR
668 *
669 * @return the exit return code (0 = success)
670 */
92fb0719
NR
671 public static int export(String luid, String typeString, String target,
672 Progress pg) {
e604986c 673 OutputType type = OutputType.valueOfNullOkUC(typeString, null);
08fe2e33 674 if (type == null) {
62c63b07
NR
675 Instance.getTraceHandler().error(
676 new Exception(trans(StringId.OUTPUT_DESC, typeString)));
08fe2e33
NR
677 return 1;
678 }
679
680 try {
92fb0719 681 Instance.getLibrary().export(luid, type, target, pg);
08fe2e33 682 } catch (IOException e) {
62c63b07 683 Instance.getTraceHandler().error(e);
08fe2e33
NR
684 return 4;
685 }
686
687 return 0;
688 }
689
690 /**
68e2c6d2
NR
691 * List the stories of the given source from the {@link LocalLibrary}
692 * (unless NULL is passed, in which case all stories will be listed).
08fe2e33 693 *
b0e88ebd
NR
694 * @param source
695 * the source to list the known stories of, or NULL to list all
333f0e7b 696 * stories
08fe2e33
NR
697 *
698 * @return the exit return code (0 = success)
699 */
b0e88ebd 700 private static int list(String source) {
0bb51c9c
NR
701 BasicReader.setDefaultReaderType(ReaderType.CLI);
702 try {
703 BasicReader.getReader().browse(source);
704 } catch (IOException e) {
705 Instance.getTraceHandler().error(e);
706 return 66;
f569d249 707 }
0bb51c9c 708
08fe2e33
NR
709 return 0;
710 }
711
712 /**
350bc060 713 * Start the current reader for this {@link Story}.
08fe2e33
NR
714 *
715 * @param story
68e2c6d2
NR
716 * the LUID of the {@link Story} in the {@link LocalLibrary}
717 * <b>or</b> the {@link Story} {@link URL}
d0114000 718 * @param chapString
08fe2e33
NR
719 * which {@link Chapter} to read (starting at 1), or NULL to get
720 * the {@link Story} description
721 * @param library
722 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
723 * {@link URL}
724 *
725 * @return the exit return code (0 = success)
726 */
d0114000 727 private static int read(String story, String chapString, boolean library) {
08fe2e33 728 try {
e42573a0 729 Reader reader = BasicReader.getReader();
08fe2e33 730 if (library) {
bc2ea776 731 reader.setMeta(story);
08fe2e33 732 } else {
bc2ea776 733 reader.setMeta(BasicReader.getUrl(story), null);
08fe2e33
NR
734 }
735
d0114000
NR
736 if (chapString != null) {
737 try {
bc2ea776 738 reader.setChapter(Integer.parseInt(chapString));
350bc060 739 reader.read(true);
d0114000 740 } catch (NumberFormatException e) {
62c63b07
NR
741 Instance.getTraceHandler().error(
742 new IOException("Chapter number cannot be parsed: "
743 + chapString, e));
d0114000
NR
744 return 2;
745 }
08fe2e33 746 } else {
350bc060 747 reader.read(true);
08fe2e33
NR
748 }
749 } catch (IOException e) {
62c63b07 750 Instance.getTraceHandler().error(e);
08fe2e33
NR
751 return 1;
752 }
753
754 return 0;
755 }
756
757 /**
758 * Convert the {@link Story} into another format.
759 *
d0114000 760 * @param urlString
08fe2e33
NR
761 * the source {@link Story} to convert
762 * @param typeString
763 * the {@link OutputType} to convert to
d0114000 764 * @param target
08fe2e33
NR
765 * the target file
766 * @param infoCover
767 * TRUE to also export the cover and info file, even if the given
768 * {@link OutputType} does not usually save them
92fb0719
NR
769 * @param pg
770 * the optional progress reporter
08fe2e33
NR
771 *
772 * @return the exit return code (0 = success)
773 */
f7460e4c 774 public static int convert(String urlString, String typeString,
92fb0719 775 String target, boolean infoCover, Progress pg) {
08fe2e33
NR
776 int exitCode = 0;
777
826e4569 778 Instance.getTraceHandler().trace("Convert: " + urlString);
d0114000 779 String sourceName = urlString;
08fe2e33 780 try {
3b2b638f 781 URL source = BasicReader.getUrl(urlString);
08fe2e33
NR
782 sourceName = source.toString();
783 if (source.toString().startsWith("file://")) {
784 sourceName = sourceName.substring("file://".length());
785 }
786
e604986c 787 OutputType type = OutputType.valueOfAllOkUC(typeString, null);
08fe2e33 788 if (type == null) {
62c63b07
NR
789 Instance.getTraceHandler().error(
790 new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE,
791 typeString)));
08fe2e33
NR
792
793 exitCode = 2;
794 } else {
795 try {
796 BasicSupport support = BasicSupport.getSupport(source);
333f0e7b 797
08fe2e33 798 if (support != null) {
350bc060
NR
799 Instance.getTraceHandler().trace(
800 "Support found: " + support.getClass());
bee7dffe
NR
801 Progress pgIn = new Progress();
802 Progress pgOut = new Progress();
803 if (pg != null) {
804 pg.setMax(2);
805 pg.addProgress(pgIn, 1);
806 pg.addProgress(pgOut, 1);
807 }
08fe2e33 808
0ffa4754 809 Story story = support.process(pgIn);
08fe2e33 810 try {
d0114000 811 target = new File(target).getAbsolutePath();
925298fd
NR
812 BasicOutput.getOutput(type, infoCover, infoCover)
813 .process(story, target, pgOut);
08fe2e33 814 } catch (IOException e) {
62c63b07
NR
815 Instance.getTraceHandler().error(
816 new IOException(trans(StringId.ERR_SAVING,
817 target), e));
08fe2e33
NR
818 exitCode = 5;
819 }
820 } else {
62c63b07
NR
821 Instance.getTraceHandler().error(
822 new IOException(trans(
823 StringId.ERR_NOT_SUPPORTED, source)));
08fe2e33
NR
824
825 exitCode = 4;
826 }
827 } catch (IOException e) {
62c63b07
NR
828 Instance.getTraceHandler().error(
829 new IOException(trans(StringId.ERR_LOADING,
830 sourceName), e));
08fe2e33
NR
831 exitCode = 3;
832 }
833 }
834 } catch (MalformedURLException e) {
62c63b07
NR
835 Instance.getTraceHandler()
836 .error(new IOException(trans(StringId.ERR_BAD_URL,
837 sourceName), e));
08fe2e33
NR
838 exitCode = 1;
839 }
840
841 return exitCode;
842 }
843
844 /**
845 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
846 *
847 * @param id
848 * the ID to translate
849 *
850 * @return the translated result
851 */
852 private static String trans(StringId id, Object... params) {
853 return Instance.getTrans().getString(id, params);
854 }
855
856 /**
d0114000
NR
857 * Display the correct syntax of the program to the user to stdout, or an
858 * error message if the syntax used was wrong on stderr.
859 *
860 * @param showHelp
861 * TRUE to show the syntax help, FALSE to show "syntax error"
08fe2e33 862 */
d0114000
NR
863 private static void syntax(boolean showHelp) {
864 if (showHelp) {
865 StringBuilder builder = new StringBuilder();
866 for (SupportType type : SupportType.values()) {
867 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
868 type.getDesc()));
869 builder.append('\n');
870 }
08fe2e33 871
d0114000
NR
872 String typesIn = builder.toString();
873 builder.setLength(0);
08fe2e33 874
d0114000
NR
875 for (OutputType type : OutputType.values()) {
876 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
4d205683 877 type.getDesc(true)));
d0114000
NR
878 builder.append('\n');
879 }
08fe2e33 880
d0114000 881 String typesOut = builder.toString();
08fe2e33 882
d0114000
NR
883 System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut));
884 } else {
885 System.err.println(trans(StringId.ERR_SYNTAX));
886 }
887 }
888
889 /**
890 * Set the default reader type for this session only (it can be changed in
891 * the configuration file, too, but this value will override it).
892 *
893 * @param readerTypeString
894 * the type
895 */
896 private static int setReaderType(String readerTypeString) {
897 try {
7de079f1
NR
898 ReaderType readerType = ReaderType.valueOf(readerTypeString
899 .toUpperCase());
d0114000
NR
900 BasicReader.setDefaultReaderType(readerType);
901 return 0;
902 } catch (IllegalArgumentException e) {
62c63b07
NR
903 Instance.getTraceHandler().error(
904 new IOException("Unknown reader type: " + readerTypeString,
905 e));
d0114000
NR
906 return 1;
907 }
08fe2e33
NR
908 }
909}