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