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