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