Add URL into .info and MetaData, work on Library
[fanfix.git] / src / be / nikiroo / fanfix / Main.java
... / ...
CommitLineData
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;
13import be.nikiroo.fanfix.reader.BasicReader;
14import be.nikiroo.fanfix.reader.BasicReader.ReaderType;
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 {
24 private enum MainAction {
25 IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER
26 }
27
28 /**
29 * Main program entry point.
30 * <p>
31 * Known environment variables:
32 * <ul>
33 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
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>
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>
54 * </ul>
55 *
56 * @param args
57 * see method description
58 */
59 public static void main(String[] args) {
60 String urlString = null;
61 String luid = null;
62 String typeString = null;
63 String chapString = null;
64 String target = null;
65 MainAction action = MainAction.HELP;
66 Boolean plusInfo = null;
67
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 }
86
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;
124 }
125 break;
126 case LIST:
127 if (typeString == null) {
128 typeString = args[i];
129 } else {
130 exitCode = 255;
131 }
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;
140 }
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;
149 }
150 break;
151 case HELP:
152 exitCode = 255;
153 break;
154 case SET_READER:
155 exitCode = setReaderType(args[i]);
156 break;
157 }
158 }
159
160 if (exitCode != 255) {
161 switch (action) {
162 case IMPORT:
163 exitCode = imprt(urlString);
164 break;
165 case EXPORT:
166 exitCode = export(urlString, typeString, target);
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:
186 break;
187 }
188 }
189
190 if (exitCode == 255) {
191 syntax(false);
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 *
230 * @param urlString
231 * the resource to import
232 *
233 * @return the exit return code (0 = success)
234 */
235 private static int imprt(String urlString) {
236 try {
237 Story story = Instance.getLibrary().imprt(getUrl(urlString));
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 *
251 * @param urlString
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 */
260 private static int export(String urlString, String typeString, String target) {
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 {
269 Story story = Instance.getLibrary().imprt(new URL(urlString));
270 Instance.getLibrary().export(story.getMeta().getLuid(), type,
271 target);
272 } catch (IOException e) {
273 Instance.syserr(e);
274 return 4;
275 }
276
277 return 0;
278 }
279
280 /**
281 * List the stories of the given type from the {@link Library} (unless NULL
282 * is passed, in which case all stories will be listed).
283 *
284 * @param typeString
285 * the {@link SupportType} to list the known stories of, or NULL
286 * to list all stories
287 *
288 * @return the exit return code (0 = success)
289 */
290 private static int list(String typeString) {
291 SupportType type = null;
292 try {
293 type = SupportType.valueOfNullOkUC(typeString);
294 } catch (Exception e) {
295 Instance.syserr(new Exception(
296 trans(StringId.INPUT_DESC, typeString), e));
297 return 1;
298 }
299
300 BasicReader.getReader().start(type);
301
302 return 0;
303 }
304
305 /**
306 * Start the CLI reader for this {@link Story}.
307 *
308 * @param story
309 * the LUID of the {@link Story} in the {@link Library} <b>or</b>
310 * the {@link Story} {@link URL}
311 * @param chapString
312 * which {@link Chapter} to read (starting at 1), or NULL to get
313 * the {@link Story} description
314 * @param library
315 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
316 * {@link URL}
317 *
318 * @return the exit return code (0 = success)
319 */
320 private static int read(String story, String chapString, boolean library) {
321 try {
322 BasicReader reader = BasicReader.getReader();
323 if (library) {
324 reader.setStory(story);
325 } else {
326 reader.setStory(getUrl(story));
327 }
328
329 if (chapString != null) {
330 try {
331 reader.read(Integer.parseInt(chapString));
332 } catch (NumberFormatException e) {
333 Instance.syserr(new IOException(
334 "Chapter number cannot be parsed: " + chapString, e));
335 return 2;
336 }
337 } else {
338 reader.read();
339 }
340 } catch (IOException e) {
341 Instance.syserr(e);
342 return 1;
343 }
344
345 return 0;
346 }
347
348 /**
349 * Convert the {@link Story} into another format.
350 *
351 * @param urlString
352 * the source {@link Story} to convert
353 * @param typeString
354 * the {@link OutputType} to convert to
355 * @param target
356 * the target file
357 * @param infoCover
358 * TRUE to also export the cover and info file, even if the given
359 * {@link OutputType} does not usually save them
360 *
361 * @return the exit return code (0 = success)
362 */
363 private static int convert(String urlString, String typeString,
364 String target, boolean infoCover) {
365 int exitCode = 0;
366
367 String sourceName = urlString;
368 try {
369 URL source = getUrl(urlString);
370 sourceName = source.toString();
371 if (source.toString().startsWith("file://")) {
372 sourceName = sourceName.substring("file://".length());
373 }
374
375 OutputType type = OutputType.valueOfAllOkUC(typeString);
376 if (type == null) {
377 Instance.syserr(new IOException(trans(
378 StringId.ERR_BAD_OUTPUT_TYPE, typeString)));
379
380 exitCode = 2;
381 } else {
382 try {
383 BasicSupport support = BasicSupport.getSupport(source);
384 if (support != null) {
385 Story story = support.process(source);
386
387 try {
388 target = new File(target).getAbsolutePath();
389 BasicOutput.getOutput(type, infoCover).process(
390 story, target);
391 } catch (IOException e) {
392 Instance.syserr(new IOException(trans(
393 StringId.ERR_SAVING, target), e));
394 exitCode = 5;
395 }
396 } else {
397 Instance.syserr(new IOException(trans(
398 StringId.ERR_NOT_SUPPORTED, source)));
399
400 exitCode = 4;
401 }
402 } catch (IOException e) {
403 Instance.syserr(new IOException(trans(StringId.ERR_LOADING,
404 sourceName), e));
405 exitCode = 3;
406 }
407 }
408 } catch (MalformedURLException e) {
409 Instance.syserr(new IOException(trans(StringId.ERR_BAD_URL,
410 sourceName), e));
411 exitCode = 1;
412 }
413
414 return exitCode;
415 }
416
417 /**
418 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
419 *
420 * @param id
421 * the ID to translate
422 *
423 * @return the translated result
424 */
425 private static String trans(StringId id, Object... params) {
426 return Instance.getTrans().getString(id, params);
427 }
428
429 /**
430 * Display the correct syntax of the program to the user to stdout, or an
431 * error message if the syntax used was wrong on stderr.
432 *
433 * @param showHelp
434 * TRUE to show the syntax help, FALSE to show "syntax error"
435 */
436 private static void syntax(boolean showHelp) {
437 if (showHelp) {
438 StringBuilder builder = new StringBuilder();
439 for (SupportType type : SupportType.values()) {
440 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
441 type.getDesc()));
442 builder.append('\n');
443 }
444
445 String typesIn = builder.toString();
446 builder.setLength(0);
447
448 for (OutputType type : OutputType.values()) {
449 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
450 type.getDesc()));
451 builder.append('\n');
452 }
453
454 String typesOut = builder.toString();
455
456 System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut));
457 } else {
458 System.err.println(trans(StringId.ERR_SYNTAX));
459 }
460 }
461
462 /**
463 * Set the default reader type for this session only (it can be changed in
464 * the configuration file, too, but this value will override it).
465 *
466 * @param readerTypeString
467 * the type
468 */
469 private static int setReaderType(String readerTypeString) {
470 try {
471 ReaderType readerType = ReaderType.valueOf(readerTypeString
472 .toUpperCase());
473 BasicReader.setDefaultReaderType(readerType);
474 return 0;
475 } catch (IllegalArgumentException e) {
476 Instance.syserr(new IOException("Unknown reader type: "
477 + readerTypeString, e));
478 return 1;
479 }
480 }
481}