X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2FMain.java;h=35365444682489d81adf7b25ed864416d6c47abe;hb=HEAD;hp=45b87c429bd9aca166a5c9558c01e95571d71109;hpb=08fe2e33007063e30fe22dc1d290f8afaa18eb1d;p=fanfix.git diff --git a/src/be/nikiroo/fanfix/Main.java b/src/be/nikiroo/fanfix/Main.java index 45b87c4..3536544 100644 --- a/src/be/nikiroo/fanfix/Main.java +++ b/src/be/nikiroo/fanfix/Main.java @@ -4,15 +4,33 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.SSLException; + +import be.nikiroo.fanfix.bundles.Config; import be.nikiroo.fanfix.bundles.StringId; import be.nikiroo.fanfix.data.Chapter; +import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.library.BasicLibrary; +import be.nikiroo.fanfix.library.CacheLibrary; +import be.nikiroo.fanfix.library.LocalLibrary; +import be.nikiroo.fanfix.library.RemoteLibrary; +import be.nikiroo.fanfix.library.RemoteLibraryServer; +import be.nikiroo.fanfix.library.WebLibrary; +import be.nikiroo.fanfix.library.WebLibraryServer; import be.nikiroo.fanfix.output.BasicOutput; import be.nikiroo.fanfix.output.BasicOutput.OutputType; +import be.nikiroo.fanfix.reader.BasicReader; import be.nikiroo.fanfix.reader.CliReader; +import be.nikiroo.fanfix.searchable.BasicSearchable; import be.nikiroo.fanfix.supported.BasicSupport; -import be.nikiroo.fanfix.supported.BasicSupport.SupportType; +import be.nikiroo.fanfix.supported.SupportType; +import be.nikiroo.utils.Progress; +import be.nikiroo.utils.Version; +import be.nikiroo.utils.VersionCheck; /** * Main program entry point. @@ -20,118 +38,779 @@ import be.nikiroo.fanfix.supported.BasicSupport.SupportType; * @author niki */ public class Main { + private enum MainAction { + IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG + } + /** * Main program entry point. *

* Known environment variables: *

+ *

+ *

* * @param args - *
    - *
  1. --import [URL]: import into library
  2. --export [id] - * [output_type] [target]: export story to target
  3. - * --convert [URL] [output_type] [target]: convert URL into - * target
  4. --read [id]: read the given story from the - * library
  5. --read-url [URL]: convert on the fly and read - * the story, without saving it
  6. --list: list the stories - * present in the library
  7. - *
+ * see method description */ public static void main(String[] args) { - int exitCode = 255; - - if (args.length > 0) { - String action = args[0]; - if (action.equals("--import")) { - if (args.length > 1) { - exitCode = imprt(args[1]); - } - } else if (action.equals("--export")) { - if (args.length > 3) { - exitCode = export(args[1], args[2], args[3]); - } - } else if (action.equals("--convert")) { - if (args.length > 3) { - exitCode = convert( - args[1], - args[2], - args[3], - args.length > 4 ? args[4].toLowerCase().equals( - "+info") : false); - } - } else if (action.equals("--list")) { - exitCode = list(args.length > 1 ? args[1] : null); - } else if (action.equals("--read-url")) { - if (args.length > 1) { - exitCode = read(args[1], args.length > 2 ? args[2] : null, - false); - } - } else if (action.equals("--read")) { - if (args.length > 1) { - exitCode = read(args[1], args.length > 2 ? args[2] : null, - true); + new Main().start(args); + } + + /** + * Start the default handling for the application. + *

+ * If specific actions were asked (with correct parameters), they will be + * forwarded to the different protected methods that you can override. + *

+ * At the end of the method, {@link Main#exit(int)} will be called; by + * default, it calls {@link System#exit(int)} if the status is not 0. + * + * @param args + * the arguments received from the system + */ + public void start(String [] args) { + // Only one line, but very important: + Instance.init(); + + String urlString = null; + String luid = null; + String sourceString = null; + String titleString = null; + String authorString = null; + String chapString = null; + String target = null; + String key = null; + MainAction action = MainAction.START; + Boolean plusInfo = null; + String host = null; + Integer port = null; + SupportType searchOn = null; + String search = null; + List tags = new ArrayList(); + Integer page = null; + Integer item = null; + + boolean noMoreActions = false; + + int exitCode = 0; + for (int i = 0; exitCode == 0 && i < args.length; i++) { + if (args[i] == null) + continue; + + // Action (--) handling: + if (!noMoreActions && args[i].startsWith("--")) { + if (args[i].equals("--")) { + noMoreActions = true; + } else { + try { + action = MainAction.valueOf(args[i].substring(2) + .toUpperCase().replace("-", "_")); + } catch (Exception e) { + Instance.getInstance().getTraceHandler() + .error(new IllegalArgumentException("Unknown action: " + args[i], e)); + exitCode = 255; + } + } + + continue; + } + + switch (action) { + case IMPORT: + if (urlString == null) { + urlString = args[i]; + } else { + exitCode = 255; + } + break; + case EXPORT: + if (luid == null) { + luid = args[i]; + } else if (sourceString == null) { + sourceString = args[i]; + } else if (target == null) { + target = args[i]; + } else { + exitCode = 255; + } + break; + case CONVERT: + if (urlString == null) { + urlString = args[i]; + } else if (sourceString == null) { + sourceString = args[i]; + } else if (target == null) { + target = args[i]; + } else if (plusInfo == null) { + if ("+info".equals(args[i])) { + plusInfo = true; + } else { + exitCode = 255; + } + } else { + exitCode = 255; + } + break; + case LIST: + if (sourceString == null) { + sourceString = args[i]; + } else { + exitCode = 255; + } + break; + case SET_SOURCE: + if (luid == null) { + luid = args[i]; + } else if (sourceString == null) { + sourceString = args[i]; + } else { + exitCode = 255; + } + break; + case SET_TITLE: + if (luid == null) { + luid = args[i]; + } else if (sourceString == null) { + titleString = args[i]; + } else { + exitCode = 255; + } + break; + case SET_AUTHOR: + if (luid == null) { + luid = args[i]; + } else if (sourceString == null) { + authorString = args[i]; + } else { + exitCode = 255; + } + break; + case READ: + if (luid == null) { + luid = args[i]; + } else if (chapString == null) { + chapString = args[i]; + } else { + exitCode = 255; + } + break; + case READ_URL: + if (urlString == null) { + urlString = args[i]; + } else if (chapString == null) { + chapString = args[i]; + } else { + exitCode = 255; + } + break; + case SEARCH: + if (searchOn == null) { + searchOn = SupportType.valueOfAllOkUC(args[i]); + + if (searchOn == null) { + Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">"); + exitCode = 41; + break; + } + + if (BasicSearchable.getSearchable(searchOn) == null) { + Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn); + exitCode = 42; + break; + } + } else if (search == null) { + search = args[i]; + } else if (page != null && page == -1) { + try { + page = Integer.parseInt(args[i]); + } catch (Exception e) { + page = -2; + } + } else if (item != null && item == -1) { + try { + item = Integer.parseInt(args[i]); + } catch (Exception e) { + item = -2; + } + } else if (page == null || item == null) { + if (page == null && "page".equals(args[i])) { + page = -1; + } else if (item == null && "item".equals(args[i])) { + item = -1; + } else { + exitCode = 255; + } + } else { + exitCode = 255; + } + break; + case SEARCH_TAG: + if (searchOn == null) { + searchOn = SupportType.valueOfAllOkUC(args[i]); + + if (searchOn == null) { + Instance.getInstance().getTraceHandler().error("Website not known: <" + args[i] + ">"); + exitCode = 255; + } + + if (BasicSearchable.getSearchable(searchOn) == null) { + Instance.getInstance().getTraceHandler().error("Website not supported: " + searchOn); + exitCode = 255; + } + } else if (page == null && item == null) { + if ("page".equals(args[i])) { + page = -1; + } else if ("item".equals(args[i])) { + item = -1; + } else { + try { + int index = Integer.parseInt(args[i]); + tags.add(index); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error("Invalid tag index: " + args[i]); + exitCode = 255; + } + } + } else if (page != null && page == -1) { + try { + page = Integer.parseInt(args[i]); + } catch (Exception e) { + page = -2; + } + } else if (item != null && item == -1) { + try { + item = Integer.parseInt(args[i]); + } catch (Exception e) { + item = -2; + } + } else if (page == null || item == null) { + if (page == null && "page".equals(args[i])) { + page = -1; + } else if (item == null && "item".equals(args[i])) { + item = -1; + } else { + exitCode = 255; + } + } else { + exitCode = 255; + } + break; + case HELP: + exitCode = 255; + break; + case START: + exitCode = 255; // not supposed to be selected by user + break; + case VERSION: + exitCode = 255; // no arguments for this option + break; + case SERVER: + exitCode = 255; // no arguments for this option + break; + case STOP_SERVER: + exitCode = 255; // no arguments for this option + break; + case REMOTE: + if (key == null) { + key = args[i]; + } else if (host == null) { + host = args[i]; + } else if (port == null) { + port = Integer.parseInt(args[i]); + + BasicLibrary lib; + if (host.startsWith("http://") + || host.startsWith("https://")) { + lib = new WebLibrary(key, host, port); + } else { + lib = new RemoteLibrary(key, host, port); + } + + lib = new CacheLibrary( + Instance.getInstance().getRemoteDir(host), lib, + Instance.getInstance().getUiConfig()); + + Instance.getInstance().setLibrary(lib); + + action = MainAction.START; + } else { + exitCode = 255; } + break; } } - if (exitCode == 255) { - syntax(); + final Progress mainProgress = new Progress(0, 80); + mainProgress.addProgressListener(new Progress.ProgressListener() { + private int current = mainProgress.getMin(); + + @Override + public void progress(Progress progress, String name) { + int diff = progress.getProgress() - current; + current += diff; + + if (diff <= 0) + return; + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < diff; i++) { + builder.append('.'); + } + + System.err.print(builder.toString()); + + if (progress.isDone()) { + System.err.println(""); + } + } + }); + Progress pg = new Progress(); + mainProgress.addProgress(pg, mainProgress.getMax()); + + VersionCheck updates = checkUpdates(); + + if (exitCode == 0) { + switch (action) { + case IMPORT: + if (updates != null) { + // we consider it read + Instance.getInstance().setVersionChecked(); + } + + try { + exitCode = imprt(BasicReader.getUrl(urlString), pg); + } catch (MalformedURLException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 1; + } + + break; + case EXPORT: + if (updates != null) { + // we consider it read + Instance.getInstance().setVersionChecked(); + } + + OutputType exportType = OutputType.valueOfNullOkUC(sourceString, null); + if (exportType == null) { + Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, sourceString))); + exitCode = 1; + break; + } + + exitCode = export(luid, exportType, target, pg); + + break; + case CONVERT: + if (updates != null) { + // we consider it read + Instance.getInstance().setVersionChecked(); + } + + OutputType convertType = OutputType.valueOfAllOkUC(sourceString, null); + if (convertType == null) { + Instance.getInstance().getTraceHandler() + .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, sourceString))); + + exitCode = 2; + break; + } + + exitCode = convert(urlString, convertType, target, + plusInfo == null ? false : plusInfo, pg); + + break; + case LIST: + exitCode = list(sourceString); + break; + case SET_SOURCE: + try { + Instance.getInstance().getLibrary().changeSource(luid, sourceString, pg); + } catch (IOException e1) { + Instance.getInstance().getTraceHandler().error(e1); + exitCode = 21; + } + break; + case SET_TITLE: + try { + Instance.getInstance().getLibrary().changeTitle(luid, titleString, pg); + } catch (IOException e1) { + Instance.getInstance().getTraceHandler().error(e1); + exitCode = 22; + } + break; + case SET_AUTHOR: + try { + Instance.getInstance().getLibrary().changeAuthor(luid, authorString, pg); + } catch (IOException e1) { + Instance.getInstance().getTraceHandler().error(e1); + exitCode = 23; + } + break; + case READ: + if (luid == null || luid.isEmpty()) { + syntax(false); + exitCode = 255; + break; + } + + try { + Integer chap = null; + if (chapString != null) { + try { + chap = Integer.parseInt(chapString); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Chapter number cannot be parsed: " + chapString, e)); + exitCode = 2; + break; + } + } + + BasicLibrary lib = Instance.getInstance().getLibrary(); + exitCode = read(lib.getStory(luid, null), chap); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + exitCode = 2; + } + + break; + case READ_URL: + if (urlString == null || urlString.isEmpty()) { + syntax(false); + exitCode = 255; + break; + } + + try { + Integer chap = null; + if (chapString != null) { + try { + chap = Integer.parseInt(chapString); + } catch (NumberFormatException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Chapter number cannot be parsed: " + chapString, e)); + exitCode = 2; + break; + } + } + + BasicSupport support = BasicSupport + .getSupport(BasicReader.getUrl(urlString)); + if (support == null) { + Instance.getInstance().getTraceHandler() + .error("URL not supported: " + urlString); + exitCode = 2; + break; + } + + exitCode = read(support.process(null), chap); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + exitCode = 2; + } + + break; + case SEARCH: + page = page == null ? 1 : page; + if (page < 0) { + Instance.getInstance().getTraceHandler().error("Incorrect page number"); + exitCode = 255; + break; + } + + item = item == null ? 0 : item; + if (item < 0) { + Instance.getInstance().getTraceHandler().error("Incorrect item number"); + exitCode = 255; + break; + } + + if (searchOn == null) { + try { + search(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 1; + } + } else if (search != null) { + try { + searchKeywords(searchOn, search, page, item); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 20; + } + } else { + exitCode = 255; + } + + break; + case SEARCH_TAG: + if (searchOn == null) { + exitCode = 255; + break; + } + + page = page == null ? 1 : page; + if (page < 0) { + Instance.getInstance().getTraceHandler().error("Incorrect page number"); + exitCode = 255; + break; + } + + item = item == null ? 0 : item; + if (item < 0) { + Instance.getInstance().getTraceHandler().error("Incorrect item number"); + exitCode = 255; + break; + } + + try { + searchTags(searchOn, page, item, + tags.toArray(new Integer[] {})); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + } + + break; + case HELP: + syntax(true); + exitCode = 0; + break; + case VERSION: + if (updates != null) { + // we consider it read + Instance.getInstance().setVersionChecked(); + } + + System.out + .println(String.format("Fanfix version %s" + + "%nhttps://github.com/nikiroo/fanfix/" + + "%n\tWritten by Nikiroo", + Version.getCurrentVersion())); + break; + case START: + try { + start(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 66; + } + break; + case SERVER: + try { + startServer(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + } + + break; + case STOP_SERVER: + // Can be given via "--remote XX XX XX" + if (key == null) { + key = Instance.getInstance().getConfig() + .getString(Config.SERVER_KEY); + + // If a subkey in RW mode exists, use it + for (String subkey : Instance.getInstance().getConfig() + .getList(Config.SERVER_ALLOWED_SUBKEYS, + new ArrayList())) { + if ((subkey + "|").contains("|rw|")) { + key = key + "|" + subkey; + break; + } + } + } + + if (port == null) { + port = Instance.getInstance().getConfig().getInteger(Config.SERVER_PORT); + } + + if (host == null) { + String mode = Instance.getInstance().getConfig() + .getString(Config.SERVER_MODE, "fanfix"); + if ("http".equals(mode)) { + host = "http://localhost"; + } else if ("https".equals(mode)) { + host = "https://localhost"; + } else if ("fanfix".equals(mode)) { + host = "fanfix://localhost"; + } + } + + if (port == null) { + System.err.println("No port given nor configured in the config file"); + exitCode = 15; + break; + } + try { + stopServer(key, host, port); + } catch (SSLException e) { + Instance.getInstance().getTraceHandler().error( + "Bad access key for remote library"); + exitCode = 43; + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + exitCode = 44; + } + + break; + case REMOTE: + exitCode = 255; // should not be reachable (REMOTE -> START) + break; + } } - if (exitCode != 0) { - System.exit(exitCode); + try { + Instance.getInstance().getTempFiles().close(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + "Cannot dispose of the temporary files", e)); } + + if (exitCode == 255) { + syntax(false); + } + + exit(exitCode); + } + + /** + * A normal invocation of the program (without parameters or at least + * without "action" parameters). + *

+ * You will probably want to override that one if you offer a user + * interface. + * + * @throws IOException + * in case of I/O error + */ + protected void start() throws IOException { + new CliReader().listBooks(null); } /** - * Return an {@link URL} from this {@link String}, be it a file path or an - * actual {@link URL}. + * Will check if updates are available, synchronously. + *

+ * For this, it will simply forward the call to + * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix". + *

+ * You may want to override it so you call the forward method with the right + * parameters (or also if you want it to be asynchronous). * - * @param sourceString - * the source + * @return the newer version information or NULL if nothing new + */ + protected VersionCheck checkUpdates() { + return checkUpdates("nikiroo/fanfix"); + } + + /** + * Will check if updates are available on a specific GitHub project. + *

+ * Will be called by {@link Main#checkUpdates()}, but if you override that + * one you mall call it with another project. * - * @return the corresponding {@link URL} + * @param githubProject + * the GitHub project, for instance "nikiroo/fanfix" * - * @throws MalformedURLException - * if this is neither a file nor a conventional {@link URL} + * @return the newer version information or NULL if nothing new */ - private static URL getUrl(String sourceString) throws MalformedURLException { - if (sourceString == null || sourceString.isEmpty()) { - throw new MalformedURLException("Empty url"); - } - - URL source = null; + protected VersionCheck checkUpdates(String githubProject) { try { - source = new URL(sourceString); - } catch (MalformedURLException e) { - File sourceFile = new File(sourceString); - source = sourceFile.toURI().toURL(); + VersionCheck updates = VersionCheck.check(githubProject, + Instance.getInstance().getTrans().getLocale()); + if (updates.isNewVersionAvailable()) { + notifyUpdates(updates); + return updates; + } + } catch (IOException e) { + // Maybe no internet. Do not report any update. } - return source; + return null; } /** - * Import the given resource into the {@link Library}. + * Notify the user about available updates. + *

+ * Will only be called when a version is available. + *

+ * Note that you can call {@link Instance#setVersionChecked()} on it if the + * user has read the information (by default, it is marked read only on + * certain other actions). + * + * @param updates + * the new version information + */ + protected void notifyUpdates(VersionCheck updates) { + // Sent to syserr so not to cause problem if one tries to capture a + // story content in text mode + System.err.println( + "A new version of the program is available at https://github.com/nikiroo/fanfix/releases"); + System.err.println(""); + for (Version v : updates.getNewer()) { + System.err.println("\tVersion " + v); + System.err.println("\t-------------"); + System.err.println(""); + for (String it : updates.getChanges().get(v)) { + System.err.println("\t- " + it); + } + System.err.println(""); + } + } + + /** + * Import the given resource into the {@link LocalLibrary}. * - * @param sourceString + * @param url * the resource to import + * @param pg + * the optional progress reporter * * @return the exit return code (0 = success) */ - private static int imprt(String sourceString) { + protected static int imprt(URL url, Progress pg) { try { - Story story = Instance.getLibrary().imprt(getUrl(sourceString)); - System.out.println(story.getMeta().getLuid() + ": \"" - + story.getMeta().getTitle() + "\" imported."); + MetaData meta = Instance.getInstance().getLibrary().imprt(url, pg); + System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported."); } catch (IOException e) { - Instance.syserr(e); + Instance.getInstance().getTraceHandler().error(e); return 1; } @@ -139,95 +818,81 @@ public class Main { } /** - * Export the {@link Story} from the {@link Library} to the given target. + * Export the {@link Story} from the {@link LocalLibrary} to the given + * target. * - * @param sourceString + * @param luid * the story LUID - * @param typeString + * @param type * the {@link OutputType} to use * @param target * the target + * @param pg + * the optional progress reporter * * @return the exit return code (0 = success) */ - private static int export(String sourceString, String typeString, - String target) { - OutputType type = OutputType.valueOfNullOkUC(typeString); - if (type == null) { - Instance.syserr(new Exception(trans(StringId.OUTPUT_DESC, - typeString))); - return 1; - } - + protected static int export(String luid, OutputType type, String target, + Progress pg) { try { - Story story = Instance.getLibrary().imprt(new URL(sourceString)); - Instance.getLibrary().export(story.getMeta().getLuid(), type, - target); + Instance.getInstance().getLibrary().export(luid, type, target, pg); } catch (IOException e) { - Instance.syserr(e); + Instance.getInstance().getTraceHandler().error(e); return 4; } return 0; } - + /** - * List the stories of the given type from the {@link Library} (unless NULL - * is passed, in which case all stories will be listed). + * List the stories of the given source from the {@link LocalLibrary} + * (unless NULL is passed, in which case all stories will be listed). * - * @param typeString - * the {@link SupportType} to list the known stories of, or NULL - * to list all stories + * @param source + * the source to list the known stories of, or NULL to list all + * stories * * @return the exit return code (0 = success) */ - private static int list(String typeString) { - SupportType type = null; + protected int list(String source) { try { - type = SupportType.valueOfNullOkUC(typeString); - } catch (Exception e) { - Instance.syserr(new Exception( - trans(StringId.INPUT_DESC, typeString), e)); - return 1; + new CliReader().listBooks(source); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + return 66; } - CliReader.list(type); - return 0; } /** - * Start the CLI reader for this {@link Story}. + * Start the current reader for this {@link Story}. * * @param story - * the LUID of the {@link Story} in the {@link Library} or - * the {@link Story} {@link URL} + * the story to read * @param chap * which {@link Chapter} to read (starting at 1), or NULL to get * the {@link Story} description - * @param library - * TRUE if the source is the {@link Story} LUID, FALSE if it is a - * {@link URL} * * @return the exit return code (0 = success) */ - private static int read(String story, String chap, boolean library) { - try { - CliReader reader; - if (library) { - reader = new CliReader(story); - } else { - reader = new CliReader(getUrl(story)); - } - - if (chap != null) { - reader.read(Integer.parseInt(chap)); - } else { - reader.read(); + protected int read(Story story, Integer chap) { + if (story != null) { + try { + if (chap == null) { + new CliReader().listChapters(story); + } else { + new CliReader().printChapter(story, chap); + } + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException("Failed to read book", e)); + return 2; } - } catch (IOException e) { - Instance.syserr(e); - return 1; + } else { + Instance.getInstance().getTraceHandler() + .error("Cannot find book: " + story); + return 2; } return 0; @@ -236,66 +901,72 @@ public class Main { /** * Convert the {@link Story} into another format. * - * @param sourceString + * @param urlString * the source {@link Story} to convert - * @param typeString + * @param type * the {@link OutputType} to convert to - * @param filename + * @param target * the target file * @param infoCover * TRUE to also export the cover and info file, even if the given * {@link OutputType} does not usually save them + * @param pg + * the optional progress reporter * * @return the exit return code (0 = success) */ - private static int convert(String sourceString, String typeString, - String filename, boolean infoCover) { + protected int convert(String urlString, OutputType type, + String target, boolean infoCover, Progress pg) { int exitCode = 0; - String sourceName = sourceString; + Instance.getInstance().getTraceHandler().trace("Convert: " + urlString); + String sourceName = urlString; try { - URL source = getUrl(sourceString); + URL source = BasicReader.getUrl(urlString); sourceName = source.toString(); - if (source.toString().startsWith("file://")) { + if (sourceName.startsWith("file://")) { sourceName = sourceName.substring("file://".length()); } - OutputType type = OutputType.valueOfAllOkUC(typeString); - if (type == null) { - Instance.syserr(new IOException(trans( - StringId.ERR_BAD_OUTPUT_TYPE, typeString))); - - exitCode = 2; - } else { - try { - BasicSupport support = BasicSupport.getSupport(source); - if (support != null) { - Story story = support.process(source); + try { + BasicSupport support = BasicSupport.getSupport(source); - try { - filename = new File(filename).getAbsolutePath(); - BasicOutput.getOutput(type, infoCover).process( - story, filename); - } catch (IOException e) { - Instance.syserr(new IOException(trans( - StringId.ERR_SAVING, filename), e)); - exitCode = 5; - } - } else { - Instance.syserr(new IOException(trans( - StringId.ERR_NOT_SUPPORTED, source))); + if (support != null) { + Instance.getInstance().getTraceHandler() + .trace("Support found: " + support.getClass()); + Progress pgIn = new Progress(); + Progress pgOut = new Progress(); + if (pg != null) { + pg.setMax(2); + pg.addProgress(pgIn, 1); + pg.addProgress(pgOut, 1); + } - exitCode = 4; + Story story = support.process(pgIn); + try { + target = new File(target).getAbsolutePath(); + BasicOutput.getOutput(type, infoCover, infoCover) + .process(story, target, pgOut); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException( + trans(StringId.ERR_SAVING, target), e)); + exitCode = 5; } - } catch (IOException e) { - Instance.syserr(new IOException(trans(StringId.ERR_LOADING, - sourceName), e)); - exitCode = 3; + } else { + Instance.getInstance().getTraceHandler() + .error(new IOException( + trans(StringId.ERR_NOT_SUPPORTED, source))); + + exitCode = 4; } + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(new IOException( + trans(StringId.ERR_LOADING, sourceName), e)); + exitCode = 3; } } catch (MalformedURLException e) { - Instance.syserr(new IOException(trans(StringId.ERR_BAD_URL, - sourceName), e)); + Instance.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e)); exitCode = 1; } @@ -303,39 +974,168 @@ public class Main { } /** - * Simple shortcut method to call {link Instance#getTrans()#getString()}. + * Display the correct syntax of the program to the user to stdout, or an + * error message if the syntax used was wrong on stderr. * - * @param id - * the ID to translate + * @param showHelp + * TRUE to show the syntax help, FALSE to show "syntax error" + */ + protected void syntax(boolean showHelp) { + if (showHelp) { + StringBuilder builder = new StringBuilder(); + for (SupportType type : SupportType.values()) { + builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), + type.getDesc())); + builder.append('\n'); + } + + String typesIn = builder.toString(); + builder.setLength(0); + + for (OutputType type : OutputType.values()) { + builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), + type.getDesc(true))); + builder.append('\n'); + } + + String typesOut = builder.toString(); + + System.out.println(trans(StringId.HELP_SYNTAX, typesIn, typesOut)); + } else { + System.err.println(trans(StringId.ERR_SYNTAX)); + } + } + + /** + * Starts a search operation (i.e., list the available web sites we can + * search on). * - * @return the translated result + * @throws IOException + * in case of I/O errors */ - private static String trans(StringId id, Object... params) { - return Instance.getTrans().getString(id, params); + protected void search() throws IOException { + new CliReader().listSearchables(); } /** - * Display the correct syntax of the program to the user. + * Search for books by keywords on the given supported web site. + * + * @param searchOn + * the web site to search on + * @param search + * the keyword to look for + * @param page + * the page of results to get, or 0 to inquire about the number + * of pages + * @param item + * the index of the book we are interested by, or 0 to query + * about how many books are in that page of results + * + * @throws IOException + * in case of I/O error */ - private static void syntax() { - StringBuilder builder = new StringBuilder(); - for (SupportType type : SupportType.values()) { - builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), - type.getDesc())); - builder.append('\n'); - } + protected void searchKeywords(SupportType searchOn, String search, + int page, Integer item) throws IOException { + new CliReader().searchBooksByKeyword(searchOn, search, page, item); + } - String typesIn = builder.toString(); - builder.setLength(0); + /** + * Search for books by tags on the given supported web site. + * + * @param searchOn + * the web site to search on + * @param page + * the page of results to get, or 0 to inquire about the number + * of pages + * @param item + * the index of the book we are interested by, or 0 to query + * about how many books are in that page of results + * @param tags + * the tags to look for + * + * @throws IOException + * in case of I/O error + */ + protected void searchTags(SupportType searchOn, Integer page, Integer item, + Integer[] tags) throws IOException { + new CliReader().searchBooksByTag(searchOn, page, item, tags); + } - for (OutputType type : OutputType.values()) { - builder.append(trans(StringId.ERR_SYNTAX_TYPE, type.toString(), - type.getDesc())); - builder.append('\n'); + /** + * Start a Fanfix server. + * + * @throws IOException + * in case of I/O errors + * @throws SSLException + * when the key was not accepted + */ + private void startServer() throws IOException { + String mode = Instance.getInstance().getConfig() + .getString(Config.SERVER_MODE, "fanfix"); + if (mode.equals("fanfix")) { + RemoteLibraryServer server = new RemoteLibraryServer(); + server.setTraceHandler(Instance.getInstance().getTraceHandler()); + server.run(); + } else if (mode.equals("http")) { + WebLibraryServer server = new WebLibraryServer(false); + server.setTraceHandler(Instance.getInstance().getTraceHandler()); + server.run(); + } else if (mode.equals("https")) { + WebLibraryServer server = new WebLibraryServer(true); + server.setTraceHandler(Instance.getInstance().getTraceHandler()); + server.run(); + } else { + throw new IOException("Unknown server mode: " + mode); } + } - String typesOut = builder.toString(); + /** + * Stop a running Fanfix server. + * + * @param key + * the key to contact the Fanfix server + * @param host + * the host on which it runs + * @param port + * the port on which it runs + * + * @throws IOException + * in case of I/O errors + * @throws SSLException + * when the key was not accepted + */ + private void stopServer(String key, String host, int port) + throws IOException, SSLException { + if (host.startsWith("http://") || host.startsWith("https://")) { + new WebLibrary(key, host, port).stop(); + } else { + new RemoteLibrary(key, host, port).stop(); + } + } - System.err.println(trans(StringId.ERR_SYNTAX, typesIn, typesOut)); + /** + * We are done and ready to exit. + *

+ * By default, it will call {@link System#exit(int)} if the status is not 0. + * + * @param status + * the exit status + */ + protected void exit(int status) { + if (status != 0) { + System.exit(status); + } + } + + /** + * Simple shortcut method to call {link Instance#getTrans()#getString()}. + * + * @param id + * the ID to translate + * + * @return the translated result + */ + static private String trans(StringId id, Object... params) { + return Instance.getInstance().getTrans().getString(id, params); } }