1 package be
.nikiroo
.fanfix
;
4 import java
.io
.IOException
;
5 import java
.net
.MalformedURLException
;
8 import be
.nikiroo
.fanfix
.bundles
.StringId
;
9 import be
.nikiroo
.fanfix
.data
.Chapter
;
10 import be
.nikiroo
.fanfix
.data
.Story
;
11 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
12 import be
.nikiroo
.fanfix
.library
.RemoteLibrary
;
13 import be
.nikiroo
.fanfix
.library
.RemoteLibraryServer
;
14 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
15 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
16 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
17 import be
.nikiroo
.fanfix
.reader
.Reader
;
18 import be
.nikiroo
.fanfix
.reader
.Reader
.ReaderType
;
19 import be
.nikiroo
.fanfix
.supported
.BasicSupport
;
20 import be
.nikiroo
.fanfix
.supported
.BasicSupport
.SupportType
;
21 import be
.nikiroo
.utils
.Progress
;
22 import be
.nikiroo
.utils
.Version
;
23 import be
.nikiroo
.utils
.serial
.Server
;
26 * Main program entry point.
31 private enum MainAction
{
32 IMPORT
, EXPORT
, CONVERT
, READ
, READ_URL
, LIST
, HELP
, SET_READER
, START
, VERSION
, SERVER
, REMOTE
,
36 * Main program entry point.
38 * Known environment variables:
40 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
41 * {@link String}s when possible</li>
42 * <li>CONFIG_DIR: a path where to look for the <tt>.properties</tt> files
43 * before taking the usual ones; they will also be saved/updated into this
44 * path when the program starts</li>
45 * <li>DEBUG: if set to 1 or 'true', the program will override the DEBUG_ERR
46 * configuration value with 'true'</li>
50 * <li>--import [URL]: import into library</li>
51 * <li>--export [id] [output_type] [target]: export story to target</li>
52 * <li>--convert [URL] [output_type] [target] (+info): convert URL into
54 * <li>--read [id] ([chapter number]): read the given story from the library
56 * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
57 * story, without saving it</li>
58 * <li>--list ([type]): list the stories present in the library</li>
59 * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
60 * for this command</li>
61 * <li>--version: get the version of the program</li>
62 * <li>--server [port]: start a server on this port</li>
63 * <li>--remote [host] [port]: use a the given remote library</li>
67 * see method description
69 public static void main(String
[] args
) {
70 String urlString
= null;
72 String sourceString
= null;
73 String chapString
= null;
75 MainAction action
= MainAction
.START
;
76 Boolean plusInfo
= null;
80 boolean noMoreActions
= false;
83 for (int i
= 0; exitCode
== 0 && i
< args
.length
; i
++) {
84 // Action (--) handling:
85 if (!noMoreActions
&& args
[i
].startsWith("--")) {
86 if (args
[i
].equals("--")) {
90 action
= MainAction
.valueOf(args
[i
].substring(2)
91 .toUpperCase().replace("-", "_"));
92 } catch (Exception e
) {
93 Instance
.syserr(new IllegalArgumentException(
94 "Unknown action: " + args
[i
], e
));
104 if (urlString
== null) {
113 } else if (sourceString
== null) {
114 sourceString
= args
[i
];
115 } else if (target
== null) {
122 if (urlString
== null) {
124 } else if (sourceString
== null) {
125 sourceString
= args
[i
];
126 } else if (target
== null) {
128 } else if (plusInfo
== null) {
129 if ("+info".equals(args
[i
])) {
139 if (sourceString
== null) {
140 sourceString
= args
[i
];
148 } else if (chapString
== null) {
149 chapString
= args
[i
];
155 if (urlString
== null) {
157 } else if (chapString
== null) {
158 chapString
= args
[i
];
167 exitCode
= setReaderType(args
[i
]);
168 action
= MainAction
.START
;
171 exitCode
= 255; // not supposed to be selected by user
174 exitCode
= 255; // no arguments for this option
178 port
= Integer
.parseInt(args
[i
]);
186 } else if (port
== null) {
187 port
= Integer
.parseInt(args
[i
]);
189 .setDefaultLibrary(new RemoteLibrary(host
, port
));
190 action
= MainAction
.START
;
198 final Progress mainProgress
= new Progress(0, 80);
199 mainProgress
.addProgressListener(new Progress
.ProgressListener() {
200 private int current
= mainProgress
.getMin();
203 public void progress(Progress progress
, String name
) {
204 int diff
= progress
.getProgress() - current
;
207 StringBuilder builder
= new StringBuilder();
208 for (int i
= 0; i
< diff
; i
++) {
212 System
.err
.print(builder
.toString());
214 if (progress
.isDone()) {
215 System
.err
.println("");
219 Progress pg
= new Progress();
220 mainProgress
.addProgress(pg
, mainProgress
.getMax());
222 VersionCheck updates
= VersionCheck
.check();
223 if (updates
.isNewVersionAvailable()) {
224 // Sent to syserr so not to cause problem if one tries to capture a
225 // story content in text mode
227 .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
228 System
.err
.println("");
229 for (Version v
: updates
.getNewer()) {
230 System
.err
.println("\tVersion " + v
);
231 System
.err
.println("\t-------------");
232 System
.err
.println("");
233 for (String item
: updates
.getChanges().get(v
)) {
234 System
.err
.println("\t- " + item
);
236 System
.err
.println("");
240 if (exitCode
!= 255) {
243 exitCode
= imprt(urlString
, pg
);
244 updates
.ok(); // we consider it read
247 exitCode
= export(luid
, sourceString
, target
, pg
);
248 updates
.ok(); // we consider it read
251 exitCode
= convert(urlString
, sourceString
, target
,
252 plusInfo
== null ?
false : plusInfo
, pg
);
253 updates
.ok(); // we consider it read
256 if (BasicReader
.getReader() == null) {
257 Instance
.syserr(new Exception(
258 "No reader type has been configured"));
262 exitCode
= list(sourceString
);
265 if (BasicReader
.getReader() == null) {
266 Instance
.syserr(new Exception(
267 "No reader type has been configured"));
271 exitCode
= read(luid
, chapString
, true);
274 if (BasicReader
.getReader() == null) {
275 Instance
.syserr(new Exception(
276 "No reader type has been configured"));
280 exitCode
= read(urlString
, chapString
, false);
291 .println(String
.format("Fanfix version %s"
292 + "\nhttps://github.com/nikiroo/fanfix/"
293 + "\n\tWritten by Nikiroo",
294 Version
.getCurrentVersion()));
295 updates
.ok(); // we consider it read
298 if (BasicReader
.getReader() == null) {
299 Instance
.syserr(new Exception(
300 "No reader type has been configured"));
304 BasicReader
.getReader().browse(null);
312 Server server
= new RemoteLibraryServer(port
);
314 System
.out
.println("Remote server started on: " + port
);
315 } catch (IOException e
) {
320 exitCode
= 255; // should not be reachable (REMOTE -> START)
325 if (exitCode
== 255) {
330 System
.exit(exitCode
);
335 * Import the given resource into the {@link LocalLibrary}.
338 * the resource to import
340 * the optional progress reporter
342 * @return the exit return code (0 = success)
344 public static int imprt(String urlString
, Progress pg
) {
346 Story story
= Instance
.getLibrary().imprt(
347 BasicReader
.getUrl(urlString
), pg
);
348 System
.out
.println(story
.getMeta().getLuid() + ": \""
349 + story
.getMeta().getTitle() + "\" imported.");
350 } catch (IOException e
) {
359 * Export the {@link Story} from the {@link LocalLibrary} to the given
365 * the {@link OutputType} to use
369 * the optional progress reporter
371 * @return the exit return code (0 = success)
373 public static int export(String luid
, String typeString
, String target
,
375 OutputType type
= OutputType
.valueOfNullOkUC(typeString
, null);
377 Instance
.syserr(new Exception(trans(StringId
.OUTPUT_DESC
,
383 Instance
.getLibrary().export(luid
, type
, target
, pg
);
384 } catch (IOException e
) {
393 * List the stories of the given source from the {@link LocalLibrary}
394 * (unless NULL is passed, in which case all stories will be listed).
397 * the source to list the known stories of, or NULL to list all
400 * @return the exit return code (0 = success)
402 private static int list(String source
) {
403 BasicReader
.getReader().browse(source
);
408 * Start the CLI reader for this {@link Story}.
411 * the LUID of the {@link Story} in the {@link LocalLibrary}
412 * <b>or</b> the {@link Story} {@link URL}
414 * which {@link Chapter} to read (starting at 1), or NULL to get
415 * the {@link Story} description
417 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
420 * @return the exit return code (0 = success)
422 private static int read(String story
, String chapString
, boolean library
) {
424 Reader reader
= BasicReader
.getReader();
426 reader
.setMeta(story
);
428 reader
.setMeta(BasicReader
.getUrl(story
), null);
431 if (chapString
!= null) {
433 reader
.setChapter(Integer
.parseInt(chapString
));
435 } catch (NumberFormatException e
) {
436 Instance
.syserr(new IOException(
437 "Chapter number cannot be parsed: " + chapString
, e
));
443 } catch (IOException e
) {
452 * Convert the {@link Story} into another format.
455 * the source {@link Story} to convert
457 * the {@link OutputType} to convert to
461 * TRUE to also export the cover and info file, even if the given
462 * {@link OutputType} does not usually save them
464 * the optional progress reporter
466 * @return the exit return code (0 = success)
468 private static int convert(String urlString
, String typeString
,
469 String target
, boolean infoCover
, Progress pg
) {
472 String sourceName
= urlString
;
474 URL source
= BasicReader
.getUrl(urlString
);
475 sourceName
= source
.toString();
476 if (source
.toString().startsWith("file://")) {
477 sourceName
= sourceName
.substring("file://".length());
480 OutputType type
= OutputType
.valueOfAllOkUC(typeString
, null);
482 Instance
.syserr(new IOException(trans(
483 StringId
.ERR_BAD_OUTPUT_TYPE
, typeString
)));
488 BasicSupport support
= BasicSupport
.getSupport(source
);
490 if (support
!= null) {
491 Progress pgIn
= new Progress();
492 Progress pgOut
= new Progress();
495 pg
.addProgress(pgIn
, 1);
496 pg
.addProgress(pgOut
, 1);
499 Story story
= support
.process(source
, pgIn
);
501 target
= new File(target
).getAbsolutePath();
502 BasicOutput
.getOutput(type
, infoCover
).process(
503 story
, target
, pgOut
);
504 } catch (IOException e
) {
505 Instance
.syserr(new IOException(trans(
506 StringId
.ERR_SAVING
, target
), e
));
510 Instance
.syserr(new IOException(trans(
511 StringId
.ERR_NOT_SUPPORTED
, source
)));
515 } catch (IOException e
) {
516 Instance
.syserr(new IOException(trans(StringId
.ERR_LOADING
,
521 } catch (MalformedURLException e
) {
522 Instance
.syserr(new IOException(trans(StringId
.ERR_BAD_URL
,
531 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
534 * the ID to translate
536 * @return the translated result
538 private static String
trans(StringId id
, Object
... params
) {
539 return Instance
.getTrans().getString(id
, params
);
543 * Display the correct syntax of the program to the user to stdout, or an
544 * error message if the syntax used was wrong on stderr.
547 * TRUE to show the syntax help, FALSE to show "syntax error"
549 private static void syntax(boolean showHelp
) {
551 StringBuilder builder
= new StringBuilder();
552 for (SupportType type
: SupportType
.values()) {
553 builder
.append(trans(StringId
.ERR_SYNTAX_TYPE
, type
.toString(),
555 builder
.append('\n');
558 String typesIn
= builder
.toString();
559 builder
.setLength(0);
561 for (OutputType type
: OutputType
.values()) {
562 builder
.append(trans(StringId
.ERR_SYNTAX_TYPE
, type
.toString(),
563 type
.getDesc(true)));
564 builder
.append('\n');
567 String typesOut
= builder
.toString();
569 System
.out
.println(trans(StringId
.HELP_SYNTAX
, typesIn
, typesOut
));
571 System
.err
.println(trans(StringId
.ERR_SYNTAX
));
576 * Set the default reader type for this session only (it can be changed in
577 * the configuration file, too, but this value will override it).
579 * @param readerTypeString
582 private static int setReaderType(String readerTypeString
) {
584 ReaderType readerType
= ReaderType
.valueOf(readerTypeString
586 BasicReader
.setDefaultReaderType(readerType
);
588 } catch (IllegalArgumentException e
) {
589 Instance
.syserr(new IOException("Unknown reader type: "
590 + readerTypeString
, e
));