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