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