1 package be
.nikiroo
.fanfix
;
4 import java
.io
.IOException
;
5 import java
.net
.MalformedURLException
;
9 import be
.nikiroo
.fanfix
.bundles
.StringId
;
10 import be
.nikiroo
.fanfix
.data
.Chapter
;
11 import be
.nikiroo
.fanfix
.data
.MetaData
;
12 import be
.nikiroo
.fanfix
.data
.Story
;
13 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
14 import be
.nikiroo
.fanfix
.library
.CacheLibrary
;
15 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
16 import be
.nikiroo
.fanfix
.library
.RemoteLibrary
;
17 import be
.nikiroo
.fanfix
.library
.RemoteLibraryServer
;
18 import be
.nikiroo
.fanfix
.output
.BasicOutput
;
19 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
20 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
21 import be
.nikiroo
.fanfix
.reader
.Reader
;
22 import be
.nikiroo
.fanfix
.reader
.Reader
.ReaderType
;
23 import be
.nikiroo
.fanfix
.supported
.BasicSupport
;
24 import be
.nikiroo
.fanfix
.supported
.SupportType
;
25 import be
.nikiroo
.utils
.Progress
;
26 import be
.nikiroo
.utils
.Version
;
27 import be
.nikiroo
.utils
.serial
.server
.ServerObject
;
30 * Main program entry point.
35 private enum MainAction
{
36 IMPORT
, EXPORT
, CONVERT
, READ
, READ_URL
, LIST
, HELP
, SET_READER
, START
, VERSION
, SERVER
, STOP_SERVER
, REMOTE
, SET_SOURCE
, SET_TITLE
, SET_AUTHOR
40 * Main program entry point.
42 * Known environment variables:
44 * <li>NOUTF: if set to 1 or 'true', the program will prefer non-unicode
45 * {@link String}s when possible</li>
46 * <li>CONFIG_DIR: a path where to look for the <tt>.properties</tt> files
47 * before taking the usual ones; they will also be saved/updated into this
48 * path when the program starts</li>
49 * <li>DEBUG: if set to 1 or 'true', the program will override the DEBUG_ERR
50 * configuration value with 'true'</li>
54 * <li>--import [URL]: import into library</li>
55 * <li>--export [id] [output_type] [target]: export story to target</li>
56 * <li>--convert [URL] [output_type] [target] (+info): convert URL into
58 * <li>--read [id] ([chapter number]): read the given story from the library
60 * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
61 * story, without saving it</li>
62 * <li>--list ([type]): list the stories present in the library</li>
63 * <li>--set-source [id] [new source]: change the source of the given story</li>
64 * <li>--set-title [id] [new title]: change the title of the given story</li>
65 * <li>--set-author [id] [new author]: change the author of the given story</li>
66 * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
67 * for this command</li>
68 * <li>--version: get the version of the program</li>
69 * <li>--server [key] [port]: start a server on this port</li>
70 * <li>--stop-server [key] [port]: stop the running server on this port if
72 * <li>--remote [key] [host] [port]: use a the given remote library</li>
76 * see method description
78 public static void main(String
[] args
) {
79 String urlString
= null;
81 String sourceString
= null;
82 String titleString
= null;
83 String authorString
= null;
84 String chapString
= null;
87 MainAction action
= MainAction
.START
;
88 Boolean plusInfo
= null;
92 boolean noMoreActions
= false;
95 for (int i
= 0; exitCode
== 0 && i
< args
.length
; i
++) {
96 // Action (--) handling:
97 if (!noMoreActions
&& args
[i
].startsWith("--")) {
98 if (args
[i
].equals("--")) {
102 action
= MainAction
.valueOf(args
[i
].substring(2)
103 .toUpperCase().replace("-", "_"));
104 } catch (Exception e
) {
105 Instance
.getTraceHandler().error(
106 new IllegalArgumentException("Unknown action: "
117 if (urlString
== null) {
126 } else if (sourceString
== null) {
127 sourceString
= args
[i
];
128 } else if (target
== null) {
135 if (urlString
== null) {
137 } else if (sourceString
== null) {
138 sourceString
= args
[i
];
139 } else if (target
== null) {
141 } else if (plusInfo
== null) {
142 if ("+info".equals(args
[i
])) {
152 if (sourceString
== null) {
153 sourceString
= args
[i
];
161 } else if (sourceString
== null) {
162 sourceString
= args
[i
];
170 } else if (sourceString
== null) {
171 titleString
= args
[i
];
179 } else if (sourceString
== null) {
180 authorString
= args
[i
];
188 } else if (chapString
== null) {
189 chapString
= args
[i
];
195 if (urlString
== null) {
197 } else if (chapString
== null) {
198 chapString
= args
[i
];
207 exitCode
= setReaderType(args
[i
]);
208 action
= MainAction
.START
;
211 exitCode
= 255; // not supposed to be selected by user
214 exitCode
= 255; // no arguments for this option
220 } else if (port
== null) {
221 port
= Integer
.parseInt(args
[i
]);
229 } else if (host
== null) {
231 } else if (port
== null) {
232 port
= Integer
.parseInt(args
[i
]);
234 BasicLibrary lib
= new RemoteLibrary(key
, host
, port
);
235 lib
= new CacheLibrary(Instance
.getRemoteDir(host
), lib
);
237 BasicReader
.setDefaultLibrary(lib
);
239 action
= MainAction
.START
;
247 final Progress mainProgress
= new Progress(0, 80);
248 mainProgress
.addProgressListener(new Progress
.ProgressListener() {
249 private int current
= mainProgress
.getMin();
252 public void progress(Progress progress
, String name
) {
253 int diff
= progress
.getProgress() - current
;
259 StringBuilder builder
= new StringBuilder();
260 for (int i
= 0; i
< diff
; i
++) {
264 System
.err
.print(builder
.toString());
266 if (progress
.isDone()) {
267 System
.err
.println("");
271 Progress pg
= new Progress();
272 mainProgress
.addProgress(pg
, mainProgress
.getMax());
274 VersionCheck updates
= VersionCheck
.check();
275 if (updates
.isNewVersionAvailable()) {
276 // Sent to syserr so not to cause problem if one tries to capture a
277 // story content in text mode
279 .println("A new version of the program is available at https://github.com/nikiroo/fanfix/releases");
280 System
.err
.println("");
281 for (Version v
: updates
.getNewer()) {
282 System
.err
.println("\tVersion " + v
);
283 System
.err
.println("\t-------------");
284 System
.err
.println("");
285 for (String item
: updates
.getChanges().get(v
)) {
286 System
.err
.println("\t- " + item
);
288 System
.err
.println("");
292 if (exitCode
!= 255) {
295 exitCode
= imprt(urlString
, pg
);
296 updates
.ok(); // we consider it read
299 exitCode
= export(luid
, sourceString
, target
, pg
);
300 updates
.ok(); // we consider it read
303 exitCode
= convert(urlString
, sourceString
, target
,
304 plusInfo
== null ?
false : plusInfo
, pg
);
305 updates
.ok(); // we consider it read
308 if (BasicReader
.getReader() == null) {
309 Instance
.getTraceHandler()
310 .error(new Exception(
311 "No reader type has been configured"));
315 exitCode
= list(sourceString
);
319 Instance
.getLibrary().changeSource(luid
, sourceString
, pg
);
320 } catch (IOException e1
) {
321 Instance
.getTraceHandler().error(e1
);
327 Instance
.getLibrary().changeTitle(luid
, titleString
, pg
);
328 } catch (IOException e1
) {
329 Instance
.getTraceHandler().error(e1
);
335 Instance
.getLibrary().changeAuthor(luid
, authorString
, pg
);
336 } catch (IOException e1
) {
337 Instance
.getTraceHandler().error(e1
);
342 if (BasicReader
.getReader() == null) {
343 Instance
.getTraceHandler()
344 .error(new Exception(
345 "No reader type has been configured"));
349 exitCode
= read(luid
, chapString
, true);
352 if (BasicReader
.getReader() == null) {
353 Instance
.getTraceHandler()
354 .error(new Exception(
355 "No reader type has been configured"));
359 exitCode
= read(urlString
, chapString
, false);
370 .println(String
.format("Fanfix version %s"
371 + "%nhttps://github.com/nikiroo/fanfix/"
372 + "%n\tWritten by Nikiroo",
373 Version
.getCurrentVersion()));
374 updates
.ok(); // we consider it read
377 if (BasicReader
.getReader() == null) {
378 Instance
.getTraceHandler()
379 .error(new Exception(
380 "No reader type has been configured"));
384 BasicReader
.getReader().browse(null);
392 ServerObject server
= new RemoteLibraryServer(key
, port
);
393 server
.setTraceHandler(Instance
.getTraceHandler());
395 } catch (IOException e
) {
396 Instance
.getTraceHandler().error(e
);
405 new RemoteLibrary(key
, host
, port
).exit();
408 exitCode
= 255; // should not be reachable (REMOTE -> START)
414 Instance
.getTempFiles().close();
415 } catch (IOException e
) {
416 Instance
.getTraceHandler()
417 .error(new IOException(
418 "Cannot dispose of the temporary files", e
));
421 if (exitCode
== 255) {
425 System
.exit(exitCode
);
429 * Import the given resource into the {@link LocalLibrary}.
432 * the resource to import
434 * the optional progress reporter
436 * @return the exit return code (0 = success)
438 public static int imprt(String urlString
, Progress pg
) {
440 Story story
= Instance
.getLibrary().imprt(
441 BasicReader
.getUrl(urlString
), pg
);
442 System
.out
.println(story
.getMeta().getLuid() + ": \""
443 + story
.getMeta().getTitle() + "\" imported.");
444 } catch (IOException e
) {
445 Instance
.getTraceHandler().error(e
);
453 * Export the {@link Story} from the {@link LocalLibrary} to the given
459 * the {@link OutputType} to use
463 * the optional progress reporter
465 * @return the exit return code (0 = success)
467 public static int export(String luid
, String typeString
, String target
,
469 OutputType type
= OutputType
.valueOfNullOkUC(typeString
, null);
471 Instance
.getTraceHandler().error(
472 new Exception(trans(StringId
.OUTPUT_DESC
, typeString
)));
477 Instance
.getLibrary().export(luid
, type
, target
, pg
);
478 } catch (IOException e
) {
479 Instance
.getTraceHandler().error(e
);
487 * List the stories of the given source from the {@link LocalLibrary}
488 * (unless NULL is passed, in which case all stories will be listed).
491 * the source to list the known stories of, or NULL to list all
494 * @return the exit return code (0 = success)
496 private static int list(String source
) {
497 List
<MetaData
> stories
;
498 stories
= BasicReader
.getReader().getLibrary().getListBySource(source
);
500 for (MetaData story
: stories
) {
502 if (story
.getAuthor() != null && !story
.getAuthor().isEmpty()) {
503 author
= " (" + story
.getAuthor() + ")";
506 System
.out
.println(story
.getLuid() + ": " + story
.getTitle()
513 * Start the current reader for this {@link Story}.
516 * the LUID of the {@link Story} in the {@link LocalLibrary}
517 * <b>or</b> the {@link Story} {@link URL}
519 * which {@link Chapter} to read (starting at 1), or NULL to get
520 * the {@link Story} description
522 * TRUE if the source is the {@link Story} LUID, FALSE if it is a
525 * @return the exit return code (0 = success)
527 private static int read(String story
, String chapString
, boolean library
) {
529 Reader reader
= BasicReader
.getReader();
531 reader
.setMeta(story
);
533 reader
.setMeta(BasicReader
.getUrl(story
), null);
536 if (chapString
!= null) {
538 reader
.setChapter(Integer
.parseInt(chapString
));
540 } catch (NumberFormatException e
) {
541 Instance
.getTraceHandler().error(
542 new IOException("Chapter number cannot be parsed: "
549 } catch (IOException e
) {
550 Instance
.getTraceHandler().error(e
);
558 * Convert the {@link Story} into another format.
561 * the source {@link Story} to convert
563 * the {@link OutputType} to convert to
567 * TRUE to also export the cover and info file, even if the given
568 * {@link OutputType} does not usually save them
570 * the optional progress reporter
572 * @return the exit return code (0 = success)
574 public static int convert(String urlString
, String typeString
,
575 String target
, boolean infoCover
, Progress pg
) {
578 Instance
.getTraceHandler().trace("Convert: " + urlString
);
579 String sourceName
= urlString
;
581 URL source
= BasicReader
.getUrl(urlString
);
582 sourceName
= source
.toString();
583 if (source
.toString().startsWith("file://")) {
584 sourceName
= sourceName
.substring("file://".length());
587 OutputType type
= OutputType
.valueOfAllOkUC(typeString
, null);
589 Instance
.getTraceHandler().error(
590 new IOException(trans(StringId
.ERR_BAD_OUTPUT_TYPE
,
596 BasicSupport support
= BasicSupport
.getSupport(source
);
598 if (support
!= null) {
599 Instance
.getTraceHandler().trace(
600 "Support found: " + support
.getClass());
601 Progress pgIn
= new Progress();
602 Progress pgOut
= new Progress();
605 pg
.addProgress(pgIn
, 1);
606 pg
.addProgress(pgOut
, 1);
609 Story story
= support
.process(pgIn
);
611 target
= new File(target
).getAbsolutePath();
612 BasicOutput
.getOutput(type
, infoCover
, infoCover
)
613 .process(story
, target
, pgOut
);
614 } catch (IOException e
) {
615 Instance
.getTraceHandler().error(
616 new IOException(trans(StringId
.ERR_SAVING
,
621 Instance
.getTraceHandler().error(
622 new IOException(trans(
623 StringId
.ERR_NOT_SUPPORTED
, source
)));
627 } catch (IOException e
) {
628 Instance
.getTraceHandler().error(
629 new IOException(trans(StringId
.ERR_LOADING
,
634 } catch (MalformedURLException e
) {
635 Instance
.getTraceHandler()
636 .error(new IOException(trans(StringId
.ERR_BAD_URL
,
645 * Simple shortcut method to call {link Instance#getTrans()#getString()}.
648 * the ID to translate
650 * @return the translated result
652 private static String
trans(StringId id
, Object
... params
) {
653 return Instance
.getTrans().getString(id
, params
);
657 * Display the correct syntax of the program to the user to stdout, or an
658 * error message if the syntax used was wrong on stderr.
661 * TRUE to show the syntax help, FALSE to show "syntax error"
663 private static void syntax(boolean showHelp
) {
665 StringBuilder builder
= new StringBuilder();
666 for (SupportType type
: SupportType
.values()) {
667 builder
.append(trans(StringId
.ERR_SYNTAX_TYPE
, type
.toString(),
669 builder
.append('\n');
672 String typesIn
= builder
.toString();
673 builder
.setLength(0);
675 for (OutputType type
: OutputType
.values()) {
676 builder
.append(trans(StringId
.ERR_SYNTAX_TYPE
, type
.toString(),
677 type
.getDesc(true)));
678 builder
.append('\n');
681 String typesOut
= builder
.toString();
683 System
.out
.println(trans(StringId
.HELP_SYNTAX
, typesIn
, typesOut
));
685 System
.err
.println(trans(StringId
.ERR_SYNTAX
));
690 * Set the default reader type for this session only (it can be changed in
691 * the configuration file, too, but this value will override it).
693 * @param readerTypeString
696 private static int setReaderType(String readerTypeString
) {
698 ReaderType readerType
= ReaderType
.valueOf(readerTypeString
700 BasicReader
.setDefaultReaderType(readerType
);
702 } catch (IllegalArgumentException e
) {
703 Instance
.getTraceHandler().error(
704 new IOException("Unknown reader type: " + readerTypeString
,