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