Improve UI, implement "Save as..." menu item
[nikiroo-utils.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;
17import be.nikiroo.utils.Progress;
18import be.nikiroo.utils.ui.UIUtils;
19
20/**
21 * Main program entry point.
22 *
23 * @author niki
24 */
25public class Main {
26 private enum MainAction {
27 IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START,
28 }
29
30 /**
31 * Main program entry point.
32 * <p>
33 * Known environment variables:
34 * <ul>
35 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
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>
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>
51 * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
52 * story, without saving it</li>
53 * <li>--list ([type]): list the stories present in the library</li>
54 * <li>--set-reader [reader type]: set the reader type to CLI or LOCAL for
55 * this command</li>
56 * </ul>
57 *
58 * @param args
59 * see method description
60 */
61 public static void main(String[] args) {
62 String urlString = null;
63 String luid = null;
64 String typeString = null;
65 String chapString = null;
66 String target = null;
67 MainAction action = MainAction.START;
68 Boolean plusInfo = null;
69
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 }
88
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;
126 }
127 break;
128 case LIST:
129 if (typeString == null) {
130 typeString = args[i];
131 } else {
132 exitCode = 255;
133 }
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;
142 }
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;
151 }
152 break;
153 case HELP:
154 exitCode = 255;
155 break;
156 case SET_READER:
157 exitCode = setReaderType(args[i]);
158 break;
159 case START:
160 exitCode = 255; // not supposed to be selected by user
161 break;
162 }
163 }
164
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
188 if (exitCode != 255) {
189 switch (action) {
190 case IMPORT:
191 exitCode = imprt(urlString, pg);
192 break;
193 case EXPORT:
194 exitCode = export(luid, typeString, target, pg);
195 break;
196 case CONVERT:
197 exitCode = convert(urlString, typeString, target,
198 plusInfo == null ? false : plusInfo, pg);
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:
214 break;
215 case START:
216 UIUtils.setLookAndFeel();
217 BasicReader.setDefaultReaderType(ReaderType.LOCAL);
218 BasicReader.getReader().start(null);
219 break;
220 }
221 }
222
223 if (exitCode == 255) {
224 syntax(false);
225 }
226
227 if (exitCode != 0) {
228 System.exit(exitCode);
229 }
230 }
231
232 /**
233 * Import the given resource into the {@link Library}.
234 *
235 * @param urlString
236 * the resource to import
237 * @param pg
238 * the optional progress reporter
239 *
240 * @return the exit return code (0 = success)
241 */
242 public static int imprt(String urlString, Progress pg) {
243 try {
244 Story story = Instance.getLibrary().imprt(
245 BasicReader.getUrl(urlString), pg);
246 System.out.println(story.getMeta().getLuid() + ": \""
247 + story.getMeta().getTitle() + "\" imported.");
248 } catch (IOException e) {
249 Instance.syserr(e);
250 return 1;
251 }
252
253 return 0;
254 }
255
256 /**
257 * Export the {@link Story} from the {@link Library} to the given target.
258 *
259 * @param luid
260 * the story LUID
261 * @param typeString
262 * the {@link OutputType} to use
263 * @param target
264 * the target
265 * @param pg
266 * the optional progress reporter
267 *
268 * @return the exit return code (0 = success)
269 */
270 public static int export(String luid, String typeString, String target,
271 Progress pg) {
272 OutputType type = OutputType.valueOfNullOkUC(typeString);
273 if (type == null) {
274 Instance.syserr(new Exception(trans(StringId.OUTPUT_DESC,
275 typeString)));
276 return 1;
277 }
278
279 try {
280 Instance.getLibrary().export(luid, type, target, pg);
281 } catch (IOException e) {
282 Instance.syserr(e);
283 return 4;
284 }
285
286 return 0;
287 }
288
289 /**
290 * List the stories of the given type from the {@link Library} (unless NULL
291 * is passed, in which case all stories will be listed).
292 *
293 * @param typeString
294 * the type to list the known stories of, or NULL to list all
295 * stories
296 *
297 * @return the exit return code (0 = success)
298 */
299 private static int list(String type) {
300 BasicReader.getReader().start(type);
301 return 0;
302 }
303
304 /**
305 * Start the CLI reader for this {@link Story}.
306 *
307 * @param story
308 * the LUID of the {@link Story} in the {@link Library} <b>or</b>
309 * the {@link Story} {@link URL}
310 * @param chapString
311 * which {@link Chapter} to read (starting at 1), or NULL to get
312 * the {@link Story} description
313 * @param library
314 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
315 * {@link URL}
316 *
317 * @return the exit return code (0 = success)
318 */
319 private static int read(String story, String chapString, boolean library) {
320 try {
321 BasicReader reader = BasicReader.getReader();
322 if (library) {
323 reader.setStory(story, null);
324 } else {
325 reader.setStory(BasicReader.getUrl(story), null);
326 }
327
328 if (chapString != null) {
329 try {
330 reader.read(Integer.parseInt(chapString));
331 } catch (NumberFormatException e) {
332 Instance.syserr(new IOException(
333 "Chapter number cannot be parsed: " + chapString, e));
334 return 2;
335 }
336 } else {
337 reader.read();
338 }
339 } catch (IOException e) {
340 Instance.syserr(e);
341 return 1;
342 }
343
344 return 0;
345 }
346
347 /**
348 * Convert the {@link Story} into another format.
349 *
350 * @param urlString
351 * the source {@link Story} to convert
352 * @param typeString
353 * the {@link OutputType} to convert to
354 * @param target
355 * the target file
356 * @param infoCover
357 * TRUE to also export the cover and info file, even if the given
358 * {@link OutputType} does not usually save them
359 * @param pg
360 * the optional progress reporter
361 *
362 * @return the exit return code (0 = success)
363 */
364 private static int convert(String urlString, String typeString,
365 String target, boolean infoCover, Progress pg) {
366 int exitCode = 0;
367
368 String sourceName = urlString;
369 try {
370 URL source = BasicReader.getUrl(urlString);
371 sourceName = source.toString();
372 if (source.toString().startsWith("file://")) {
373 sourceName = sourceName.substring("file://".length());
374 }
375
376 OutputType type = OutputType.valueOfAllOkUC(typeString);
377 if (type == null) {
378 Instance.syserr(new IOException(trans(
379 StringId.ERR_BAD_OUTPUT_TYPE, typeString)));
380
381 exitCode = 2;
382 } else {
383 try {
384 BasicSupport support = BasicSupport.getSupport(source);
385
386 if (support != null) {
387 Progress pgIn = new Progress();
388 Progress pgOut = new Progress();
389 if (pg != null) {
390 pg.setMax(2);
391 pg.addProgress(pgIn, 1);
392 pg.addProgress(pgOut, 1);
393 }
394
395 Story story = support.process(source, pgIn);
396 try {
397 target = new File(target).getAbsolutePath();
398 BasicOutput.getOutput(type, infoCover).process(
399 story, target, pgOut);
400 } catch (IOException e) {
401 Instance.syserr(new IOException(trans(
402 StringId.ERR_SAVING, target), e));
403 exitCode = 5;
404 }
405 } else {
406 Instance.syserr(new IOException(trans(
407 StringId.ERR_NOT_SUPPORTED, source)));
408
409 exitCode = 4;
410 }
411 } catch (IOException e) {
412 Instance.syserr(new IOException(trans(StringId.ERR_LOADING,
413 sourceName), e));
414 exitCode = 3;
415 }
416 }
417 } catch (MalformedURLException e) {
418 Instance.syserr(new IOException(trans(StringId.ERR_BAD_URL,
419 sourceName), e));
420 exitCode = 1;
421 }
422
423 return exitCode;
424 }
425
426 /**
427 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
428 *
429 * @param id
430 * the ID to translate
431 *
432 * @return the translated result
433 */
434 private static String trans(StringId id, Object... params) {
435 return Instance.getTrans().getString(id, params);
436 }
437
438 /**
439 * Display the correct syntax of the program to the user to stdout, or an
440 * error message if the syntax used was wrong on stderr.
441 *
442 * @param showHelp
443 * TRUE to show the syntax help, FALSE to show "syntax error"
444 */
445 private static void syntax(boolean showHelp) {
446 if (showHelp) {
447 StringBuilder builder = new StringBuilder();
448 for (SupportType type : SupportType.values()) {
449 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
450 type.getDesc()));
451 builder.append('\n');
452 }
453
454 String typesIn = builder.toString();
455 builder.setLength(0);
456
457 for (OutputType type : OutputType.values()) {
458 builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(),
459 type.getDesc(true)));
460 builder.append('\n');
461 }
462
463 String typesOut = builder.toString();
464
465 System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut));
466 } else {
467 System.err.println(trans(StringId.ERR_SYNTAX));
468 }
469 }
470
471 /**
472 * Set the default reader type for this session only (it can be changed in
473 * the configuration file, too, but this value will override it).
474 *
475 * @param readerTypeString
476 * the type
477 */
478 private static int setReaderType(String readerTypeString) {
479 try {
480 ReaderType readerType = ReaderType.valueOf(readerTypeString
481 .toUpperCase());
482 BasicReader.setDefaultReaderType(readerType);
483 return 0;
484 } catch (IllegalArgumentException e) {
485 Instance.syserr(new IOException("Unknown reader type: "
486 + readerTypeString, e));
487 return 1;
488 }
489 }
490}