Fix configure.sh again (--tui=no + jar was not ok)
[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;
7
8import be.nikiroo.fanfix.bundles.StringId;
9import be.nikiroo.fanfix.data.Chapter;
10import be.nikiroo.fanfix.data.Story;
11import be.nikiroo.fanfix.output.BasicOutput;
12import be.nikiroo.fanfix.output.BasicOutput.OutputType;
3727aae2 13import be.nikiroo.fanfix.reader.BasicReader;
d0114000 14import be.nikiroo.fanfix.reader.BasicReader.ReaderType;
08fe2e33
NR
15import be.nikiroo.fanfix.supported.BasicSupport;
16import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
3b2b638f 17import be.nikiroo.utils.Progress;
39c3c689 18import be.nikiroo.utils.Version;
b0e88ebd 19import be.nikiroo.utils.serial.Server;
08fe2e33
NR
20
21/**
22 * Main program entry point.
23 *
24 * @author niki
25 */
26public class Main {
d0114000 27 private enum MainAction {
b0e88ebd 28 IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START, VERSION, SERVER, REMOTE,
d0114000
NR
29 }
30
08fe2e33
NR
31 /**
32 * Main program entry point.
33 * <p>
34 * Known environment variables:
35 * <ul>
d0114000 36 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
08fe2e33
NR
37 * {@link String}s when possible</li>
38 * <li>CONFIG_DIR: a path where to look for the <tt>.properties</tt> files
edd46289
NR
39 * before taking the usual ones; they will also be saved/updated into this
40 * path when the program starts</li>
d0114000
NR
41 * <li>DEBUG: if set to 1 or 'true', the program will override the DEBUG_ERR
42 * configuration value with 'true'</li>
43 * </ul>
44 * <p>
45 * <ul>
46 * <li>--import [URL]: import into library</li>
47 * <li>--export [id] [output_type] [target]: export story to target</li>
48 * <li>--convert [URL] [output_type] [target] (+info): convert URL into
49 * target</li>
50 * <li>--read [id] ([chapter number]): read the given story from the library
51 * </li>
333f0e7b 52 * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
d0114000 53 * story, without saving it</li>
333f0e7b 54 * <li>--list ([type]): list the stories present in the library</li>
c1873e56
NR
55 * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
56 * for this command</li>
39c3c689 57 * <li>--version: get the version of the program</li>
b0e88ebd
NR
58 * <li>--server [port]: start a server on this port</li>
59 * <li>--remote [host] [port]: use a the given remote library</li>
08fe2e33
NR
60 * </ul>
61 *
62 * @param args
d0114000 63 * see method description
08fe2e33
NR
64 */
65 public static void main(String[] args) {
d0114000
NR
66 String urlString = null;
67 String luid = null;
b0e88ebd 68 String sourceString = null;
d0114000
NR
69 String chapString = null;
70 String target = null;
333f0e7b 71 MainAction action = MainAction.START;
d0114000 72 Boolean plusInfo = null;
b0e88ebd
NR
73 String host = null;
74 Integer port = null;
73ce17ef 75
d0114000
NR
76 boolean noMoreActions = false;
77
78 int exitCode = 0;
79 for (int i = 0; exitCode == 0 && i < args.length; i++) {
80 // Action (--) handling:
81 if (!noMoreActions && args[i].startsWith("--")) {
82 if (args[i].equals("--")) {
83 noMoreActions = true;
84 } else {
85 try {
86 action = MainAction.valueOf(args[i].substring(2)
87 .toUpperCase().replace("-", "_"));
88 } catch (Exception e) {
89 Instance.syserr(new IllegalArgumentException(
90 "Unknown action: " + args[i], e));
91 exitCode = 255;
92 }
93 }
08fe2e33 94
d0114000
NR
95 continue;
96 }
97
98 switch (action) {
99 case IMPORT:
100 if (urlString == null) {
101 urlString = args[i];
102 } else {
103 exitCode = 255;
104 }
105 break;
106 case EXPORT:
107 if (luid == null) {
108 luid = args[i];
b0e88ebd
NR
109 } else if (sourceString == null) {
110 sourceString = args[i];
d0114000
NR
111 } else if (target == null) {
112 target = args[i];
113 } else {
114 exitCode = 255;
115 }
116 break;
117 case CONVERT:
118 if (urlString == null) {
119 urlString = args[i];
b0e88ebd
NR
120 } else if (sourceString == null) {
121 sourceString = args[i];
d0114000
NR
122 } else if (target == null) {
123 target = args[i];
124 } else if (plusInfo == null) {
125 if ("+info".equals(args[i])) {
126 plusInfo = true;
127 } else {
128 exitCode = 255;
129 }
130 } else {
131 exitCode = 255;
08fe2e33 132 }
d0114000
NR
133 break;
134 case LIST:
b0e88ebd
NR
135 if (sourceString == null) {
136 sourceString = args[i];
d0114000
NR
137 } else {
138 exitCode = 255;
08fe2e33 139 }
d0114000
NR
140 break;
141 case READ:
142 if (luid == null) {
143 luid = args[i];
144 } else if (chapString == null) {
145 chapString = args[i];
146 } else {
147 exitCode = 255;
08fe2e33 148 }
d0114000
NR
149 break;
150 case READ_URL:
151 if (urlString == null) {
152 urlString = args[i];
153 } else if (chapString == null) {
154 chapString = args[i];
155 } else {
156 exitCode = 255;
08fe2e33 157 }
d0114000
NR
158 break;
159 case HELP:
160 exitCode = 255;
161 break;
162 case SET_READER:
7de079f1 163 exitCode = setReaderType(args[i]);
c1873e56 164 action = MainAction.START;
d0114000 165 break;
333f0e7b
NR
166 case START:
167 exitCode = 255; // not supposed to be selected by user
168 break;
39c3c689
NR
169 case VERSION:
170 exitCode = 255; // no arguments for this option
b0e88ebd
NR
171 break;
172 case SERVER:
173 if (port == null) {
174 port = Integer.parseInt(args[i]);
175 } else {
176 exitCode = 255;
177 }
178 break;
179 case REMOTE:
180 if (host == null) {
181 host = args[i];
182 } else if (port == null) {
183 port = Integer.parseInt(args[i]);
68e2c6d2
NR
184 BasicReader
185 .setDefaultLibrary(new RemoteLibrary(host, port));
b0e88ebd
NR
186 action = MainAction.START;
187 } else {
188 exitCode = 255;
189 }
190 break;
d0114000
NR
191 }
192 }
193
92fb0719
NR
194 final Progress mainProgress = new Progress(0, 80);
195 mainProgress.addProgressListener(new Progress.ProgressListener() {
196 private int current = mainProgress.getMin();
197
198 public void progress(Progress progress, String name) {
199 int diff = progress.getProgress() - current;
200 current += diff;
201
202 StringBuilder builder = new StringBuilder();
203 for (int i = 0; i < diff; i++) {
204 builder.append('.');
205 }
206
207 System.err.print(builder.toString());
208
209 if (progress.isDone()) {
210 System.err.println("");
211 }
212 }
213 });
214 Progress pg = new Progress();
215 mainProgress.addProgress(pg, mainProgress.getMax());
216
b42117f1
NR
217 VersionCheck updates = VersionCheck.check();
218 if (updates.isNewVersionAvailable()) {
219 // Sent to syserr so not to cause problem if one tries to capture a
220 // story content in text mode
221 System.err
222 .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
223 System.err.println("");
224 for (Version v : updates.getNewer()) {
225 System.err.println("\tVersion " + v);
226 System.err.println("\t-------------");
227 System.err.println("");
228 for (String item : updates.getChanges().get(v)) {
229 System.err.println("\t- " + item);
230 }
231 System.err.println("");
232 }
233 }
234
d0114000
NR
235 if (exitCode != 255) {
236 switch (action) {
237 case IMPORT:
92fb0719 238 exitCode = imprt(urlString, pg);
b42117f1 239 updates.ok(); // we consider it read
d0114000
NR
240 break;
241 case EXPORT:
b0e88ebd 242 exitCode = export(luid, sourceString, target, pg);
b42117f1 243 updates.ok(); // we consider it read
d0114000
NR
244 break;
245 case CONVERT:
b0e88ebd 246 exitCode = convert(urlString, sourceString, target,
92fb0719 247 plusInfo == null ? false : plusInfo, pg);
b42117f1 248 updates.ok(); // we consider it read
d0114000
NR
249 break;
250 case LIST:
b0e88ebd 251 exitCode = list(sourceString);
d0114000
NR
252 break;
253 case READ:
254 exitCode = read(luid, chapString, true);
255 break;
256 case READ_URL:
257 exitCode = read(urlString, chapString, false);
258 break;
259 case HELP:
260 syntax(true);
261 exitCode = 0;
262 break;
263 case SET_READER:
b0e88ebd 264 exitCode = 255;
d0114000 265 break;
39c3c689
NR
266 case VERSION:
267 System.out
268 .println(String.format("Fanfix version %s"
269 + "\nhttps://github.com/nikiroo/fanfix/"
270 + "\n\tWritten by Nikiroo",
271 Version.getCurrentVersion()));
b42117f1 272 updates.ok(); // we consider it read
39c3c689 273 break;
333f0e7b 274 case START:
b0e88ebd
NR
275 BasicReader.getReader().browse(null);
276 break;
277 case SERVER:
278 if (port == null) {
279 exitCode = 255;
280 break;
281 }
282 try {
283 Server server = new RemoteLibraryServer(port);
284 server.start();
285 System.out.println("Remote server started on: " + port);
286 } catch (IOException e) {
287 Instance.syserr(e);
288 }
289 return;
290 case REMOTE:
291 exitCode = 255;
333f0e7b 292 break;
08fe2e33
NR
293 }
294 }
295
296 if (exitCode == 255) {
d0114000 297 syntax(false);
08fe2e33
NR
298 }
299
300 if (exitCode != 0) {
301 System.exit(exitCode);
302 }
303 }
304
08fe2e33 305 /**
68e2c6d2 306 * Import the given resource into the {@link LocalLibrary}.
08fe2e33 307 *
d0114000 308 * @param urlString
08fe2e33 309 * the resource to import
92fb0719
NR
310 * @param pg
311 * the optional progress reporter
08fe2e33
NR
312 *
313 * @return the exit return code (0 = success)
314 */
92fb0719 315 public static int imprt(String urlString, Progress pg) {
08fe2e33 316 try {
3b2b638f
NR
317 Story story = Instance.getLibrary().imprt(
318 BasicReader.getUrl(urlString), pg);
08fe2e33
NR
319 System.out.println(story.getMeta().getLuid() + ": \""
320 + story.getMeta().getTitle() + "\" imported.");
321 } catch (IOException e) {
322 Instance.syserr(e);
323 return 1;
324 }
325
326 return 0;
327 }
328
329 /**
68e2c6d2
NR
330 * Export the {@link Story} from the {@link LocalLibrary} to the given
331 * target.
08fe2e33 332 *
73ce17ef 333 * @param luid
08fe2e33
NR
334 * the story LUID
335 * @param typeString
336 * the {@link OutputType} to use
337 * @param target
338 * the target
92fb0719
NR
339 * @param pg
340 * the optional progress reporter
08fe2e33
NR
341 *
342 * @return the exit return code (0 = success)
343 */
92fb0719
NR
344 public static int export(String luid, String typeString, String target,
345 Progress pg) {
08fe2e33
NR
346 OutputType type = OutputType.valueOfNullOkUC(typeString);
347 if (type == null) {
348 Instance.syserr(new Exception(trans(StringId.OUTPUT_DESC,
349 typeString)));
350 return 1;
351 }
352
353 try {
92fb0719 354 Instance.getLibrary().export(luid, type, target, pg);
08fe2e33
NR
355 } catch (IOException e) {
356 Instance.syserr(e);
357 return 4;
358 }
359
360 return 0;
361 }
362
363 /**
68e2c6d2
NR
364 * List the stories of the given source from the {@link LocalLibrary}
365 * (unless NULL is passed, in which case all stories will be listed).
08fe2e33 366 *
b0e88ebd
NR
367 * @param source
368 * the source to list the known stories of, or NULL to list all
333f0e7b 369 * stories
08fe2e33
NR
370 *
371 * @return the exit return code (0 = success)
372 */
b0e88ebd
NR
373 private static int list(String source) {
374 BasicReader.getReader().browse(source);
08fe2e33
NR
375 return 0;
376 }
377
378 /**
379 * Start the CLI reader for this {@link Story}.
380 *
381 * @param story
68e2c6d2
NR
382 * the LUID of the {@link Story} in the {@link LocalLibrary}
383 * <b>or</b> the {@link Story} {@link URL}
d0114000 384 * @param chapString
08fe2e33
NR
385 * which {@link Chapter} to read (starting at 1), or NULL to get
386 * the {@link Story} description
387 * @param library
388 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
389 * {@link URL}
390 *
391 * @return the exit return code (0 = success)
392 */
d0114000 393 private static int read(String story, String chapString, boolean library) {
08fe2e33 394 try {
3727aae2 395 BasicReader reader = BasicReader.getReader();
08fe2e33 396 if (library) {
92fb0719 397 reader.setStory(story, null);
08fe2e33 398 } else {
3b2b638f 399 reader.setStory(BasicReader.getUrl(story), null);
08fe2e33
NR
400 }
401
d0114000
NR
402 if (chapString != null) {
403 try {
404 reader.read(Integer.parseInt(chapString));
405 } catch (NumberFormatException e) {
406 Instance.syserr(new IOException(
407 "Chapter number cannot be parsed: " + chapString, e));
408 return 2;
409 }
08fe2e33
NR
410 } else {
411 reader.read();
412 }
413 } catch (IOException e) {
414 Instance.syserr(e);
415 return 1;
416 }
417
418 return 0;
419 }
420
421 /**
422 * Convert the {@link Story} into another format.
423 *
d0114000 424 * @param urlString
08fe2e33
NR
425 * the source {@link Story} to convert
426 * @param typeString
427 * the {@link OutputType} to convert to
d0114000 428 * @param target
08fe2e33
NR
429 * the target file
430 * @param infoCover
431 * TRUE to also export the cover and info file, even if the given
432 * {@link OutputType} does not usually save them
92fb0719
NR
433 * @param pg
434 * the optional progress reporter
08fe2e33
NR
435 *
436 * @return the exit return code (0 = success)
437 */
d0114000 438 private static int convert(String urlString, String typeString,
92fb0719 439 String target, boolean infoCover, Progress pg) {
08fe2e33
NR
440 int exitCode = 0;
441
d0114000 442 String sourceName = urlString;
08fe2e33 443 try {
3b2b638f 444 URL source = BasicReader.getUrl(urlString);
08fe2e33
NR
445 sourceName = source.toString();
446 if (source.toString().startsWith("file://")) {
447 sourceName = sourceName.substring("file://".length());
448 }
449
450 OutputType type = OutputType.valueOfAllOkUC(typeString);
451 if (type == null) {
452 Instance.syserr(new IOException(trans(
453 StringId.ERR_BAD_OUTPUT_TYPE, typeString)));
454
455 exitCode = 2;
456 } else {
457 try {
458 BasicSupport support = BasicSupport.getSupport(source);
333f0e7b 459
08fe2e33 460 if (support != null) {
bee7dffe
NR
461 Progress pgIn = new Progress();
462 Progress pgOut = new Progress();
463 if (pg != null) {
464 pg.setMax(2);
465 pg.addProgress(pgIn, 1);
466 pg.addProgress(pgOut, 1);
467 }
08fe2e33 468
bee7dffe 469 Story story = support.process(source, pgIn);
08fe2e33 470 try {
d0114000 471 target = new File(target).getAbsolutePath();
08fe2e33 472 BasicOutput.getOutput(type, infoCover).process(
bee7dffe 473 story, target, pgOut);
08fe2e33
NR
474 } catch (IOException e) {
475 Instance.syserr(new IOException(trans(
d0114000 476 StringId.ERR_SAVING, target), e));
08fe2e33
NR
477 exitCode = 5;
478 }
479 } else {
480 Instance.syserr(new IOException(trans(
481 StringId.ERR_NOT_SUPPORTED, source)));
482
483 exitCode = 4;
484 }
485 } catch (IOException e) {
486 Instance.syserr(new IOException(trans(StringId.ERR_LOADING,
487 sourceName), e));
488 exitCode = 3;
489 }
490 }
491 } catch (MalformedURLException e) {
492 Instance.syserr(new IOException(trans(StringId.ERR_BAD_URL,
493 sourceName), e));
494 exitCode = 1;
495 }
496
497 return exitCode;
498 }
499
500 /**
501 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
502 *
503 * @param id
504 * the ID to translate
505 *
506 * @return the translated result
507 */
508 private static String trans(StringId id, Object... params) {
509 return Instance.getTrans().getString(id, params);
510 }
511
512 /**
d0114000
NR
513 * Display the correct syntax of the program to the user to stdout, or an
514 * error message if the syntax used was wrong on stderr.
515 *
516 * @param showHelp
517 * TRUE to show the syntax help, FALSE to show "syntax error"
08fe2e33 518 */
d0114000
NR
519 private static void syntax(boolean showHelp) {
520 if (showHelp) {
521 StringBuilder builder = new StringBuilder();
522 for (SupportType type : SupportType.values()) {
523 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
524 type.getDesc()));
525 builder.append('\n');
526 }
08fe2e33 527
d0114000
NR
528 String typesIn = builder.toString();
529 builder.setLength(0);
08fe2e33 530
d0114000
NR
531 for (OutputType type : OutputType.values()) {
532 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
4d205683 533 type.getDesc(true)));
d0114000
NR
534 builder.append('\n');
535 }
08fe2e33 536
d0114000 537 String typesOut = builder.toString();
08fe2e33 538
d0114000
NR
539 System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut));
540 } else {
541 System.err.println(trans(StringId.ERR_SYNTAX));
542 }
543 }
544
545 /**
546 * Set the default reader type for this session only (it can be changed in
547 * the configuration file, too, but this value will override it).
548 *
549 * @param readerTypeString
550 * the type
551 */
552 private static int setReaderType(String readerTypeString) {
553 try {
7de079f1
NR
554 ReaderType readerType = ReaderType.valueOf(readerTypeString
555 .toUpperCase());
d0114000
NR
556 BasicReader.setDefaultReaderType(readerType);
557 return 0;
558 } catch (IllegalArgumentException e) {
559 Instance.syserr(new IOException("Unknown reader type: "
560 + readerTypeString, e));
561 return 1;
562 }
08fe2e33
NR
563 }
564}