Merge master
authorNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 18:42:37 +0000 (20:42 +0200)
committerNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 18:42:37 +0000 (20:42 +0200)
19 files changed:
Main.java
VersionCheck.java
library/RemoteLibrary.java
test/BasicSupportDeprecatedTest.java [new file with mode: 0644]
test/BasicSupportUtilitiesTest.java [new file with mode: 0644]
test/ConversionTest.java [new file with mode: 0644]
test/LibraryTest.java [new file with mode: 0644]
test/Test.java [new file with mode: 0644]
test/expected_test.story/cbz.cbz [new file with mode: 0644]
test/expected_test.story/epub.epub [new file with mode: 0644]
test/expected_test.story/html/html.info [new file with mode: 0644]
test/expected_test.story/html/html.txt [new file with mode: 0644]
test/expected_test.story/html/index.html [new file with mode: 0644]
test/expected_test.story/html/style.css [new file with mode: 0644]
test/expected_test.story/info_text [new file with mode: 0644]
test/expected_test.story/info_text.info [new file with mode: 0644]
test/expected_test.story/latex.tex [new file with mode: 0644]
test/expected_test.story/text.txt [new file with mode: 0644]
test/test.story [new file with mode: 0644]

index 8d72803aaad4b0c35b38bd676a4961b2da06c454..b49cf0fb13e38338c293166027882e3df3876e67 100644 (file)
--- a/Main.java
+++ b/Main.java
@@ -85,6 +85,22 @@ public class Main {
         *            see method description
         */
        public static void main(String[] args) {
+               new Main().start(args);
+       }
+
+       /**
+        * Start the default handling for the application.
+        * <p>
+        * If specific actions were asked (with correct parameters), they will be
+        * forwarded to the different protected methods that you can override.
+        * <p>
+        * 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();
 
@@ -373,38 +389,52 @@ public class Main {
                Progress pg = new Progress();
                mainProgress.addProgress(pg, mainProgress.getMax());
 
-               VersionCheck updates = VersionCheck.check();
-               if (updates.isNewVersionAvailable()) {
-                       // 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("");
-                       }
-               }
+               VersionCheck updates = checkUpdates();
 
                if (exitCode == 0) {
                        switch (action) {
                        case IMPORT:
-                               exitCode = imprt(urlString, pg);
-                               updates.ok(); // we consider it read
+                               if (updates != null)
+                                       updates.ok(); // we consider it read
+                               
+                               try {
+                                       exitCode = imprt(BasicReader.getUrl(urlString), pg);
+                               } catch (MalformedURLException e) {
+                                       Instance.getInstance().getTraceHandler().error(e);
+                                       exitCode = 1;
+                               }
+                               
                                break;
                        case EXPORT:
-                               exitCode = export(luid, sourceString, target, pg);
-                               updates.ok(); // we consider it read
+                               if (updates != null)
+                                       updates.ok(); // we consider it read
+                               
+                               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:
-                               exitCode = convert(urlString, sourceString, target,
+                               if (updates != null)
+                                       updates.ok(); // we consider it read
+                               
+                               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);
-                               updates.ok(); // we consider it read
+                               
                                break;
                        case LIST:
                                exitCode = list(sourceString);
@@ -441,8 +471,20 @@ public class Main {
                                }
 
                                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), chapString);
+                                       exitCode = read(lib.getStory(luid, null), chap);
                                } catch (IOException e) {
                                        Instance.getInstance().getTraceHandler()
                                                        .error(new IOException("Failed to read book", e));
@@ -458,6 +500,18 @@ public class Main {
                                }
 
                                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) {
@@ -467,7 +521,7 @@ public class Main {
                                                break;
                                        }
 
-                                       exitCode = read(support.process(null), chapString);
+                                       exitCode = read(support.process(null), chap);
                                } catch (IOException e) {
                                        Instance.getInstance().getTraceHandler()
                                                        .error(new IOException("Failed to read book", e));
@@ -490,19 +544,22 @@ public class Main {
                                        break;
                                }
 
-                               try {
-                                       if (searchOn == null) {
-                                               new CliReader().listSearchables();
-                                       } else if (search != null) {
-
-                                               new CliReader().searchBooksByKeyword(searchOn, search, page,
-                                                               item);
-                                       } else {
-                                               exitCode = 255;
+                               if (searchOn == null) {
+                                       try {
+                                               search();
+                                       } catch (IOException e) {
+                                               Instance.getInstance().getTraceHandler().error(e);
+                                               exitCode = 1;
                                        }
-                               } catch (IOException e1) {
-                                       Instance.getInstance().getTraceHandler().error(e1);
-                                       exitCode = 20;
+                               } else if (search != null) {
+                                       try {
+                                               searchKeywords(searchOn, search, page, item);
+                                       } catch (IOException e) {
+                                               Instance.getInstance().getTraceHandler().error(e);
+                                               exitCode = 20;
+                                       }
+                               } else {
+                                       exitCode = 255;
                                }
 
                                break;
@@ -527,10 +584,10 @@ public class Main {
                                }
 
                                try {
-                                       new CliReader().searchBooksByTag(searchOn, page, item,
-                                                       tags.toArray(new Integer[] {}));
-                               } catch (IOException e1) {
-                                       Instance.getInstance().getTraceHandler().error(e1);
+                                       searchTags(searchOn, page, item,
+                                       tags.toArray(new Integer[] {}));
+                               } catch (IOException e) {
+                                       Instance.getInstance().getTraceHandler().error(e);
                                }
 
                                break;
@@ -539,16 +596,18 @@ public class Main {
                                exitCode = 0;
                                break;
                        case VERSION:
+                               if (updates != null)
+                                       updates.ok(); // we consider it read
+                               
                                System.out
                                                .println(String.format("Fanfix version %s"
                                                                + "%nhttps://github.com/nikiroo/fanfix/"
                                                                + "%n\tWritten by Nikiroo",
                                                                Version.getCurrentVersion()));
-                               updates.ok(); // we consider it read
                                break;
                        case START:
                                try {
-                                       new CliReader().listBooks(null);
+                                       start();
                                } catch (IOException e) {
                                        Instance.getInstance().getTraceHandler().error(e);
                                        exitCode = 66;
@@ -563,13 +622,12 @@ public class Main {
                                        break;
                                }
                                try {
-                                       ServerObject server = new RemoteLibraryServer(key, port);
-                                       server.setTraceHandler(Instance.getInstance().getTraceHandler());
-                                       server.run();
+                                       startServer(key, port);
                                } catch (IOException e) {
                                        Instance.getInstance().getTraceHandler().error(e);
                                }
-                               return;
+                               
+                               break;
                        case STOP_SERVER:
                                // Can be given via "--remote XX XX XX"
                                if (key == null)
@@ -583,7 +641,7 @@ public class Main {
                                        break;
                                }
                                try {
-                                       new RemoteLibrary(key, host, port).exit();
+                                       stopServer(key, host, port);
                                } catch (SSLException e) {
                                        Instance.getInstance().getTraceHandler().error(
                                                        "Bad access key for remote library");
@@ -603,29 +661,106 @@ public class Main {
                try {
                        Instance.getInstance().getTempFiles().close();
                } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(new IOException("Cannot dispose of the temporary files", e));
+                       Instance.getInstance().getTraceHandler().error(new IOException(
+                                       "Cannot dispose of the temporary files", e));
                }
 
                if (exitCode == 255) {
                        syntax(false);
                }
 
-               System.exit(exitCode);
+               exit(exitCode);
+       }
+       
+       /**
+        * A normal invocation of the program (without parameters or at least
+        * without "action" parameters).
+        * <p>
+        * 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);
+       }
+
+       /**
+        * Will check if updates are available.
+        * <p>
+        * For this, it will simply forward the call to
+        * {@link Main#checkUpdates(String)} with a value of "nikiroo/fanfix".
+        * 
+        * @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.
+        * <p>
+        * Will be called by {@link Main#checkUpdates()}, but if you override that
+        * one you mall call it with another project.
+        * 
+        * @param githubProject
+        *            the GitHub project, for instance "nikiroo/fanfix"
+        * 
+        * @return the newer version information or NULL if nothing new
+        */
+       protected VersionCheck checkUpdates(String githubProject) {
+               VersionCheck updates = VersionCheck.check(githubProject);
+               if (updates.isNewVersionAvailable()) {
+                       notifyUpdates(updates);
+                       return updates;
+               }
+               
+               return null;
        }
 
+       /**
+        * Notify the user about available updates.
+        * <p>
+        * Will only be called when a version is available.
+        * <p>
+        * Note that you can call {@link VersionCheck#ok()} 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 urlString
+        * @param url
         *            the resource to import
         * @param pg
         *            the optional progress reporter
         * 
         * @return the exit return code (0 = success)
         */
-       public static int imprt(String urlString, Progress pg) {
+       protected static int imprt(URL url, Progress pg) {
                try {
-                       MetaData meta = Instance.getInstance().getLibrary().imprt(BasicReader.getUrl(urlString), pg);
+                       MetaData meta = Instance.getInstance().getLibrary().imprt(url, pg);
                        System.out.println(meta.getLuid() + ": \"" + meta.getTitle() + "\" imported.");
                } catch (IOException e) {
                        Instance.getInstance().getTraceHandler().error(e);
@@ -641,7 +776,7 @@ public class Main {
         * 
         * @param luid
         *            the story LUID
-        * @param typeString
+        * @param type
         *            the {@link OutputType} to use
         * @param target
         *            the target
@@ -650,14 +785,8 @@ public class Main {
         * 
         * @return the exit return code (0 = success)
         */
-       public static int export(String luid, String typeString, String target,
+       protected static int export(String luid, OutputType type, String target,
                        Progress pg) {
-               OutputType type = OutputType.valueOfNullOkUC(typeString, null);
-               if (type == null) {
-                       Instance.getInstance().getTraceHandler().error(new Exception(trans(StringId.OUTPUT_DESC, typeString)));
-                       return 1;
-               }
-
                try {
                        Instance.getInstance().getLibrary().export(luid, type, target, pg);
                } catch (IOException e) {
@@ -667,7 +796,7 @@ public class Main {
 
                return 0;
        }
-
+       
        /**
         * List the stories of the given source from the {@link LocalLibrary}
         * (unless NULL is passed, in which case all stories will be listed).
@@ -678,7 +807,7 @@ public class Main {
         * 
         * @return the exit return code (0 = success)
         */
-       private static int list(String source) {
+       protected int list(String source) {
                try {
                        new CliReader().listBooks(source);
                } catch (IOException e) {
@@ -694,24 +823,13 @@ public class Main {
         * 
         * @param story
         *            the story to read
-        * @param chapString
+        * @param chap
         *            which {@link Chapter} to read (starting at 1), or NULL to get
         *            the {@link Story} description
         * 
         * @return the exit return code (0 = success)
         */
-       private static int read(Story story, String chapString) {
-               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));
-                               return 2;
-                       }
-               }
-
+       protected int read(Story story, Integer chap) {
                if (story != null) {
                        try {
                                if (chap == null) {
@@ -726,7 +844,7 @@ public class Main {
                        }
                } else {
                        Instance.getInstance().getTraceHandler()
-                                       .error("Cannot find book: " + chapString);
+                                       .error("Cannot find book: " + story);
                        return 2;
                }
 
@@ -738,7 +856,7 @@ public class Main {
         * 
         * @param urlString
         *            the source {@link Story} to convert
-        * @param typeString
+        * @param type
         *            the {@link OutputType} to convert to
         * @param target
         *            the target file
@@ -750,7 +868,7 @@ public class Main {
         * 
         * @return the exit return code (0 = success)
         */
-       public static int convert(String urlString, String typeString,
+       protected int convert(String urlString, OutputType type,
                        String target, boolean infoCover, Progress pg) {
                int exitCode = 0;
 
@@ -763,46 +881,42 @@ public class Main {
                                sourceName = sourceName.substring("file://".length());
                        }
 
-                       OutputType type = OutputType.valueOfAllOkUC(typeString, null);
-                       if (type == null) {
-                               Instance.getInstance().getTraceHandler()
-                                               .error(new IOException(trans(StringId.ERR_BAD_OUTPUT_TYPE, typeString)));
+                       try {
+                               BasicSupport support = BasicSupport.getSupport(source);
 
-                               exitCode = 2;
-                       } else {
-                               try {
-                                       BasicSupport support = BasicSupport.getSupport(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);
-                                               }
+                               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);
+                                       }
 
-                                               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;
-                                               }
-                                       } else {
+                                       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_NOT_SUPPORTED, source)));
-
-                                               exitCode = 4;
+                                                               .error(new IOException(
+                                                                               trans(StringId.ERR_SAVING, target), e));
+                                               exitCode = 5;
                                        }
-                               } catch (IOException e) {
+                               } else {
                                        Instance.getInstance().getTraceHandler()
-                                                       .error(new IOException(trans(StringId.ERR_LOADING, sourceName), e));
-                                       exitCode = 3;
+                                                       .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.getInstance().getTraceHandler().error(new IOException(trans(StringId.ERR_BAD_URL, sourceName), e));
@@ -812,18 +926,6 @@ public class Main {
                return exitCode;
        }
 
-       /**
-        * Simple shortcut method to call {link Instance#getTrans()#getString()}.
-        * 
-        * @param id
-        *            the ID to translate
-        * 
-        * @return the translated result
-        */
-       private static String trans(StringId id, Object... params) {
-               return Instance.getInstance().getTrans().getString(id, params);
-       }
-
        /**
         * Display the correct syntax of the program to the user to stdout, or an
         * error message if the syntax used was wrong on stderr.
@@ -831,7 +933,7 @@ public class Main {
         * @param showHelp
         *            TRUE to show the syntax help, FALSE to show "syntax error"
         */
-       private static void syntax(boolean showHelp) {
+       protected void syntax(boolean showHelp) {
                if (showHelp) {
                        StringBuilder builder = new StringBuilder();
                        for (SupportType type : SupportType.values()) {
@@ -856,4 +958,125 @@ public class Main {
                        System.err.println(trans(StringId.ERR_SYNTAX));
                }
        }
+
+       /**
+        * Starts a search operation (i.e., list the available web sites we can
+        * search on).
+        * 
+        * @throws IOException
+        *             in case of I/O errors
+        */
+       protected void search() throws IOException {
+               new CliReader().listSearchables();
+       }
+
+       /**
+        * 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
+        */
+       protected void searchKeywords(SupportType searchOn, String search,
+                       int page, Integer item) throws IOException {
+               new CliReader().searchBooksByKeyword(searchOn, search, page, item);
+       }
+
+       /**
+        * 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);
+       }
+
+       /**
+        * Start a Fanfix server.
+        * 
+        * @param key
+        *            the key taht will be needed to contact the Fanfix server
+        * @param port
+        *            the port on which to run
+        * 
+        * @throws IOException
+        *             in case of I/O errors
+        * @throws SSLException
+        *             when the key was not accepted
+        */
+       private void startServer(String key, int port) throws IOException {
+               ServerObject server = new RemoteLibraryServer(key, port);
+               server.setTraceHandler(Instance.getInstance().getTraceHandler());
+               server.run();
+       }
+
+       /**
+        * Stop a running Fanfix server.
+        * 
+        * @param key
+        *            the key to contact the Fanfix server
+        * @param host
+        *            the host on which it runs (NULL means localhost)
+        * @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, Integer port)
+                       throws IOException, SSLException {
+               new RemoteLibrary(key, host, port).exit();
+       }
+
+       /**
+        * We are done and ready to exit.
+        * <p>
+        * 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);
+       }
 }
index f64159abb97c3fe3236bc272710402a1e32631f7..fc631b0a6d1f9d8326457606ff38e9884b5357b3 100644 (file)
@@ -20,7 +20,7 @@ import be.nikiroo.utils.Version;
  * @author niki
  */
 public class VersionCheck {
-       private static final String base = "https://github.com/nikiroo/fanfix/raw/master/changelog${LANG}.md";
+       private static final String base = "https://github.com/${PROJECT}/raw/master/changelog${LANG}.md";
 
        private Version current;
        private List<Version> newer;
@@ -100,16 +100,23 @@ public class VersionCheck {
         * Check if there are available {@link Version}s of this program more recent
         * than the current one.
         * 
+        * @param githubProject
+        *            the GitHub project to check on, for instance "nikiroo/fanfix"
+        * 
         * @return a {@link VersionCheck}
         */
-       public static VersionCheck check() {
+       public static VersionCheck check(String githubProject) {
                Version current = Version.getCurrentVersion();
                List<Version> newer = new ArrayList<Version>();
                Map<Version, List<String>> changes = new HashMap<Version, List<String>>();
 
                if (Instance.getInstance().isVersionCheckNeeded()) {
                        try {
-                               // Prepare the URLs according to the user's language
+                               // Use the right project:
+                               String base = VersionCheck.base.replace("${PROJECT}",
+                                               githubProject);
+                               
+                               // Prepare the URLs according to the user's language:
                                Locale lang = Instance.getInstance().getTrans().getLocale();
                                String fr = lang.getLanguage();
                                String BE = lang.getCountry().replace(".UTF8", "");
index bbe772a8c90168c658fa985461e92c58f13bb8d0..a4f00ceff53546eaf1849e526c656add37fa6d17 100644 (file)
@@ -435,9 +435,11 @@ public class RemoteLibrary extends BasicLibrary {
         * Stop the server.
         * 
         * @throws IOException
-        *             in case of I/O error (including bad key)
+        *             in case of I/O errors
+        * @throws SSLException
+        *             when the key was not accepted
         */
-       public void exit() throws IOException {
+       public void exit() throws IOException, SSLException {
                connectRemoteAction(new RemoteAction() {
                        @Override
                        public void action(ConnectActionClientObject action)
diff --git a/test/BasicSupportDeprecatedTest.java b/test/BasicSupportDeprecatedTest.java
new file mode 100644 (file)
index 0000000..c8c4b40
--- /dev/null
@@ -0,0 +1,451 @@
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringId;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix.supported.BasicSupport_Deprecated;
+import be.nikiroo.fanfix.supported.SupportType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BasicSupportDeprecatedTest extends TestLauncher {
+       // quote chars
+       private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE);
+       private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE);
+       private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+       private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+       public BasicSupportDeprecatedTest(String[] args) {
+               super("BasicSupportDeprecated", args);
+
+               addSeries(new TestLauncher("General", args) {
+                       {
+                               addTest(new TestCase("BasicSupport.makeParagraphs()") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+
+                                                       @Override
+                                                       public void fixBlanksBreaks(List<Paragraph> paras) {
+                                                       }
+
+                                                       @Override
+                                                       public List<Paragraph> requotify(Paragraph para) {
+                                                               List<Paragraph> paras = new ArrayList<Paragraph>(
+                                                                               1);
+                                                               paras.add(para);
+                                                               return paras;
+                                                       }
+                                               };
+
+                                               List<Paragraph> paras = null;
+
+                                               paras = support.makeParagraphs(null, "", null);
+                                               assertEquals(
+                                                               "An empty content should not generate paragraphs",
+                                                               0, paras.size());
+
+                                               paras = support.makeParagraphs(null,
+                                                               "Line 1</p><p>Line 2</p><p>Line 3</p>", null);
+                                               assertEquals(5, paras.size());
+                                               assertEquals("Line 1", paras.get(0).getContent());
+                                               assertEquals(ParagraphType.BLANK, paras.get(1)
+                                                               .getType());
+                                               assertEquals("Line 2", paras.get(2).getContent());
+                                               assertEquals(ParagraphType.BLANK, paras.get(3)
+                                                               .getType());
+                                               assertEquals("Line 3", paras.get(4).getContent());
+
+                                               paras = support.makeParagraphs(null,
+                                                               "<p>Line1</p><p>Line2</p><p>Line3</p>", null);
+                                               assertEquals(6, paras.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.removeDoubleBlanks()") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+                                               };
+
+                                               List<Paragraph> paras = null;
+
+                                               paras = support
+                                                               .makeParagraphs(
+                                                                               null,
+                                                                               "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>",
+                                                                               null);
+                                               assertEquals(5, paras.size());
+
+                                               paras = support
+                                                               .makeParagraphs(
+                                                                               null,
+                                                                               "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>* * *",
+                                                                               null);
+                                               assertEquals(5, paras.size());
+
+                                               paras = support.makeParagraphs(null, "1<p>* * *<p>2",
+                                                               null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null,
+                                                               "1<p><br/><p>* * *<p>2", null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null,
+                                                               "1<p>* * *<br/><p><br><p>2", null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null,
+                                                               "1<p><br/><br>* * *<br/><p><br><p>2", null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() quotes") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+                                               };
+
+                                               Paragraph para;
+
+                                               // sanity check
+                                               para = support.processPara("");
+                                               assertEquals(ParagraphType.BLANK, para.getType());
+                                               //
+
+                                               para = support.processPara("\"Yes, my Lord!\"");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("«Yes, my Lord!»");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("'Yes, my Lord!'");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+                                                               para.getContent());
+
+                                               para = support.processPara("‹Yes, my Lord!›");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+                                                               para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase(
+                                               "BasicSupport.processPara() double-simple quotes") {
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               super.setUp();
+
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+
+                                               super.tearDown();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+                                               };
+
+                                               Paragraph para;
+
+                                               para = support.processPara("''Yes, my Lord!''");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("‹‹Yes, my Lord!››");
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() apostrophe") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+                                               };
+
+                                               Paragraph para;
+
+                                               String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge";
+                                               para = support.processPara(text);
+                                               assertEquals(ParagraphType.NORMAL, para.getType());
+                                               assertEquals(text, para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() words count") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty() {
+                                                       @Override
+                                                       protected boolean isHtml() {
+                                                               return true;
+                                                       }
+                                               };
+
+                                               Paragraph para;
+
+                                               para = support.processPara("«Yes, my Lord!»");
+                                               assertEquals(3, para.getWords());
+
+                                               para = support.processPara("One, twee, trois.");
+                                               assertEquals(3, para.getWords());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.requotify() words count") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportEmpty support = new BasicSupportEmpty();
+
+                                               char openDoubleQuote = Instance.getInstance().getTrans()
+                                                               .getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+                                               char closeDoubleQuote = Instance.getInstance().getTrans()
+                                                               .getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+                                               String content = null;
+                                               Paragraph para = null;
+                                               List<Paragraph> paras = null;
+                                               long words = 0;
+
+                                               content = "One, twee, trois.";
+                                               para = new Paragraph(ParagraphType.NORMAL, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals("Bad words count in a single paragraph",
+                                                               para.getWords(), words);
+
+                                               content = "Such WoW! So Web2.0! With Colours!";
+                                               para = new Paragraph(ParagraphType.NORMAL, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals("Bad words count in a single paragraph",
+                                                               para.getWords(), words);
+
+                                               content = openDoubleQuote + "Such a good idea!"
+                                                               + closeDoubleQuote
+                                                               + ", she said. This ought to be a new para.";
+                                               para = new Paragraph(ParagraphType.QUOTE, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals(
+                                                               "Bad words count in a requotified paragraph",
+                                                               para.getWords(), words);
+                                       }
+                               });
+                       }
+               });
+
+               addSeries(new TestLauncher("Text", args) {
+                       {
+                               addTest(new TestCase("Chapter detection simple") {
+                                       private File tmp;
+
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               tmp = File.createTempFile("fanfix-text-file_", ".test");
+                                               IOUtils.writeSmallFile(tmp.getParentFile(),
+                                                               tmp.getName(), "TITLE"
+                                                                               + "\n"//
+                                                                               + "By nona"
+                                                                               + "\n" //
+                                                                               + "\n" //
+                                                                               + "Chapter 0: Resumé" + "\n" + "\n"
+                                                                               + "'sume." + "\n" + "\n"
+                                                                               + "Chapter 1: chap1" + "\n" + "\n"
+                                                                               + "Fanfan." + "\n" + "\n"
+                                                                               + "Chapter 2: Chap2" + "\n" + "\n" //
+                                                                               + "Tulipe." + "\n");
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+                                               tmp.delete();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupport support = BasicSupport.getSupport(
+                                                               SupportType.TEXT, tmp.toURI().toURL());
+
+                                               Story story = support.process(null);
+
+                                               assertEquals(2, story.getChapters().size());
+                                               assertEquals(1, story.getChapters().get(1)
+                                                               .getParagraphs().size());
+                                               assertEquals("Tulipe.", story.getChapters().get(1)
+                                                               .getParagraphs().get(0).getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("Chapter detection with String 'Chapter'") {
+                                       private File tmp;
+
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               tmp = File.createTempFile("fanfix-text-file_", ".test");
+                                               IOUtils.writeSmallFile(tmp.getParentFile(),
+                                                               tmp.getName(), "TITLE"
+                                                                               + "\n"//
+                                                                               + "By nona"
+                                                                               + "\n" //
+                                                                               + "\n" //
+                                                                               + "Chapter 0: Resumé" + "\n" + "\n"
+                                                                               + "'sume." + "\n" + "\n"
+                                                                               + "Chapter 1: chap1" + "\n" + "\n"
+                                                                               + "Chapter fout-la-merde" + "\n"
+                                                                               + "\n"//
+                                                                               + "Fanfan." + "\n" + "\n"
+                                                                               + "Chapter 2: Chap2" + "\n" + "\n" //
+                                                                               + "Tulipe." + "\n");
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+                                               tmp.delete();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupport support = BasicSupport.getSupport(
+                                                               SupportType.TEXT, tmp.toURI().toURL());
+
+                                               Story story = support.process(null);
+
+                                               assertEquals(2, story.getChapters().size());
+                                               assertEquals(1, story.getChapters().get(1)
+                                                               .getParagraphs().size());
+                                               assertEquals("Tulipe.", story.getChapters().get(1)
+                                                               .getParagraphs().get(0).getContent());
+                                       }
+                               });
+                       }
+               });
+       }
+
+       private class BasicSupportEmpty extends BasicSupport_Deprecated {
+               @Override
+               protected boolean supports(URL url) {
+                       return false;
+               }
+
+               @Override
+               protected boolean isHtml() {
+                       return false;
+               }
+
+               @Override
+               protected MetaData getMeta(URL source, InputStream in)
+                               throws IOException {
+                       return null;
+               }
+
+               @Override
+               protected String getDesc(URL source, InputStream in) throws IOException {
+                       return null;
+               }
+
+               @Override
+               protected List<Entry<String, URL>> getChapters(URL source,
+                               InputStream in, Progress pg) throws IOException {
+                       return null;
+               }
+
+               @Override
+               protected String getChapterContent(URL source, InputStream in,
+                               int number, Progress pg) throws IOException {
+                       return null;
+               }
+
+               @Override
+               // and make it public!
+               public List<Paragraph> makeParagraphs(URL source, String content,
+                               Progress pg) throws IOException {
+                       return super.makeParagraphs(source, content, pg);
+               }
+
+               @Override
+               // and make it public!
+               public void fixBlanksBreaks(List<Paragraph> paras) {
+                       super.fixBlanksBreaks(paras);
+               }
+
+               @Override
+               // and make it public!
+               public Paragraph processPara(String line) {
+                       return super.processPara(line);
+               }
+
+               @Override
+               // and make it public!
+               public List<Paragraph> requotify(Paragraph para) {
+                       return super.requotify(para);
+               }
+       }
+}
diff --git a/test/BasicSupportUtilitiesTest.java b/test/BasicSupportUtilitiesTest.java
new file mode 100644 (file)
index 0000000..9cec220
--- /dev/null
@@ -0,0 +1,397 @@
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringId;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix.supported.BasicSupportHelper;
+import be.nikiroo.fanfix.supported.BasicSupportImages;
+import be.nikiroo.fanfix.supported.BasicSupportPara;
+import be.nikiroo.fanfix.supported.SupportType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BasicSupportUtilitiesTest extends TestLauncher {
+       // quote chars
+       private char openQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_SINGLE_QUOTE);
+       private char closeQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_SINGLE_QUOTE);
+       private char openDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+       private char closeDoubleQuote = Instance.getInstance().getTrans().getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+       
+       public BasicSupportUtilitiesTest(String[] args) {
+               super("BasicSupportUtilities", args);
+               
+               addSeries(new TestLauncher("General", args) {
+                       {
+                               addTest(new TestCase("BasicSupport.makeParagraphs()") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic bsPara = new BasicSupportParaPublic() {
+                                                       @Override
+                                                       public void fixBlanksBreaks(List<Paragraph> paras) {
+                                                       }
+
+                                                       @Override
+                                                       public List<Paragraph> requotify(Paragraph para, boolean html) {
+                                                               List<Paragraph> paras = new ArrayList<Paragraph>(
+                                                                               1);
+                                                               paras.add(para);
+                                                               return paras;
+                                                       }
+                                               };
+
+                                               List<Paragraph> paras = null;
+
+                                               paras = bsPara.makeParagraphs(null, null, "", true, null);
+                                               assertEquals(
+                                                               "An empty content should not generate paragraphs",
+                                                               0, paras.size());
+
+                                               paras = bsPara.makeParagraphs(null, null,
+                                                               "Line 1</p><p>Line 2</p><p>Line 3</p>", true, null);
+                                               assertEquals(5, paras.size());
+                                               assertEquals("Line 1", paras.get(0).getContent());
+                                               assertEquals(ParagraphType.BLANK, paras.get(1)
+                                                               .getType());
+                                               assertEquals("Line 2", paras.get(2).getContent());
+                                               assertEquals(ParagraphType.BLANK, paras.get(3)
+                                                               .getType());
+                                               assertEquals("Line 3", paras.get(4).getContent());
+
+                                               paras = bsPara.makeParagraphs(null, null,
+                                                               "<p>Line1</p><p>Line2</p><p>Line3</p>", true, null);
+                                               assertEquals(6, paras.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.removeDoubleBlanks()") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               List<Paragraph> paras = null;
+
+                                               paras = support
+                                                               .makeParagraphs(
+                                                                               null,
+                                                                               null,
+                                                                               "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>",
+                                                                               true,
+                                                                               null);
+                                               assertEquals(5, paras.size());
+
+                                               paras = support
+                                                               .makeParagraphs(
+                                                                               null,
+                                                                               null,
+                                                                               "<p>Line1</p><p>Line2</p><p>Line3<br/><br><p></p>* * *",
+                                                                               true,
+                                                                               null);
+                                               assertEquals(5, paras.size());
+
+                                               paras = support.makeParagraphs(null, null, "1<p>* * *<p>2",
+                                                               true, null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null, null,
+                                                               "1<p><br/><p>* * *<p>2", true, null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null, null,
+                                                               "1<p>* * *<br/><p><br><p>2", true, null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+
+                                               paras = support.makeParagraphs(null, null,
+                                                               "1<p><br/><br>* * *<br/><p><br><p>2", true, null);
+                                               assertEquals(3, paras.size());
+                                               assertEquals(ParagraphType.BREAK, paras.get(1)
+                                                               .getType());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() quotes") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               Paragraph para;
+
+                                               // sanity check
+                                               para = support.processPara("", true);
+                                               assertEquals(ParagraphType.BLANK, para.getType());
+                                               //
+
+                                               para = support.processPara("\"Yes, my Lord!\"", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("«Yes, my Lord!»", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("'Yes, my Lord!'", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+                                                               para.getContent());
+
+                                               para = support.processPara("‹Yes, my Lord!›", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openQuote + "Yes, my Lord!" + closeQuote,
+                                                               para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase(
+                                               "BasicSupport.processPara() double-simple quotes") {
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               super.setUp();
+
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+
+                                               super.tearDown();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               Paragraph para;
+
+                                               para = support.processPara("''Yes, my Lord!''", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+
+                                               para = support.processPara("‹‹Yes, my Lord!››", true);
+                                               assertEquals(ParagraphType.QUOTE, para.getType());
+                                               assertEquals(openDoubleQuote + "Yes, my Lord!"
+                                                               + closeDoubleQuote, para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() apostrophe") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               Paragraph para;
+
+                                               String text = "Nous étions en été, mais cela aurait être l'hiver quand nous n'étions encore qu'à Aubeuge";
+                                               para = support.processPara(text, true);
+                                               assertEquals(ParagraphType.NORMAL, para.getType());
+                                               assertEquals(text, para.getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.processPara() words count") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               Paragraph para;
+
+                                               para = support.processPara("«Yes, my Lord!»", true);
+                                               assertEquals(3, para.getWords());
+
+                                               para = support.processPara("One, twee, trois.", true);
+                                               assertEquals(3, para.getWords());
+                                       }
+                               });
+
+                               addTest(new TestCase("BasicSupport.requotify() words count") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupportParaPublic support = new BasicSupportParaPublic();
+
+                                               char openDoubleQuote = Instance.getInstance().getTrans()
+                                                               .getCharacter(StringId.OPEN_DOUBLE_QUOTE);
+                                               char closeDoubleQuote = Instance.getInstance().getTrans()
+                                                               .getCharacter(StringId.CLOSE_DOUBLE_QUOTE);
+
+                                               String content = null;
+                                               Paragraph para = null;
+                                               List<Paragraph> paras = null;
+                                               long words = 0;
+
+                                               content = "One, twee, trois.";
+                                               para = new Paragraph(ParagraphType.NORMAL, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para, false);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals("Bad words count in a single paragraph",
+                                                               para.getWords(), words);
+
+                                               content = "Such WoW! So Web2.0! With Colours!";
+                                               para = new Paragraph(ParagraphType.NORMAL, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para, false);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals("Bad words count in a single paragraph",
+                                                               para.getWords(), words);
+
+                                               content = openDoubleQuote + "Such a good idea!"
+                                                               + closeDoubleQuote
+                                                               + ", she said. This ought to be a new para.";
+                                               para = new Paragraph(ParagraphType.QUOTE, content,
+                                                               content.split(" ").length);
+                                               paras = support.requotify(para, false);
+                                               words = 0;
+                                               for (Paragraph p : paras) {
+                                                       words += p.getWords();
+                                               }
+                                               assertEquals(
+                                                               "Bad words count in a requotified paragraph",
+                                                               para.getWords(), words);
+                                       }
+                               });
+                       }
+               });
+
+               addSeries(new TestLauncher("Text", args) {
+                       {
+                               addTest(new TestCase("Chapter detection simple") {
+                                       private File tmp;
+
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               tmp = File.createTempFile("fanfix-text-file_", ".test");
+                                               IOUtils.writeSmallFile(tmp.getParentFile(),
+                                                               tmp.getName(), "TITLE"
+                                                                               + "\n"//
+                                                                               + "By nona"
+                                                                               + "\n" //
+                                                                               + "\n" //
+                                                                               + "Chapter 0: Resumé" + "\n" + "\n"
+                                                                               + "'sume." + "\n" + "\n"
+                                                                               + "Chapter 1: chap1" + "\n" + "\n"
+                                                                               + "Fanfan." + "\n" + "\n"
+                                                                               + "Chapter 2: Chap2" + "\n" + "\n" //
+                                                                               + "Tulipe." + "\n");
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+                                               tmp.delete();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupport support = BasicSupport.getSupport(
+                                                               SupportType.TEXT, tmp.toURI().toURL());
+
+                                               Story story = support.process(null);
+
+                                               assertEquals(2, story.getChapters().size());
+                                               assertEquals(1, story.getChapters().get(1)
+                                                               .getParagraphs().size());
+                                               assertEquals("Tulipe.", story.getChapters().get(1)
+                                                               .getParagraphs().get(0).getContent());
+                                       }
+                               });
+
+                               addTest(new TestCase("Chapter detection with String 'Chapter'") {
+                                       private File tmp;
+
+                                       @Override
+                                       public void setUp() throws Exception {
+                                               tmp = File.createTempFile("fanfix-text-file_", ".test");
+                                               IOUtils.writeSmallFile(tmp.getParentFile(),
+                                                               tmp.getName(), "TITLE"
+                                                                               + "\n"//
+                                                                               + "By nona"
+                                                                               + "\n" //
+                                                                               + "\n" //
+                                                                               + "Chapter 0: Resumé" + "\n" + "\n"
+                                                                               + "'sume." + "\n" + "\n"
+                                                                               + "Chapter 1: chap1" + "\n" + "\n"
+                                                                               + "Chapter fout-la-merde" + "\n"
+                                                                               + "\n"//
+                                                                               + "Fanfan." + "\n" + "\n"
+                                                                               + "Chapter 2: Chap2" + "\n" + "\n" //
+                                                                               + "Tulipe." + "\n");
+                                       }
+
+                                       @Override
+                                       public void tearDown() throws Exception {
+                                               tmp.delete();
+                                       }
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               BasicSupport support = BasicSupport.getSupport(
+                                                               SupportType.TEXT, tmp.toURI().toURL());
+
+                                               Story story = support.process(null);
+
+                                               assertEquals(2, story.getChapters().size());
+                                               assertEquals(1, story.getChapters().get(1)
+                                                               .getParagraphs().size());
+                                               assertEquals("Tulipe.", story.getChapters().get(1)
+                                                               .getParagraphs().get(0).getContent());
+                                       }
+                               });
+                       }
+               });
+       }
+       
+       class BasicSupportParaPublic extends BasicSupportPara {
+               public BasicSupportParaPublic() {
+                       super(new BasicSupportHelper(), new BasicSupportImages());
+               }
+               
+               @Override
+               // and make it public!
+               public Paragraph makeParagraph(BasicSupport support, URL source,
+                               String line, boolean html) {
+                       return super.makeParagraph(support, source, line, html);
+               }
+               
+               @Override
+               // and make it public!
+               public List<Paragraph> makeParagraphs(BasicSupport support,
+                               URL source, String content, boolean html, Progress pg)
+                               throws IOException {
+                       return super.makeParagraphs(support, source, content, html, pg);
+               }
+               
+               @Override
+               // and make it public!
+               public Paragraph processPara(String line, boolean html) {
+                       return super.processPara(line, html);
+               }
+               
+               @Override
+               // and make it public!
+               public List<Paragraph> requotify(Paragraph para, boolean html) {
+                       return super.requotify(para, html);
+               }
+       }
+}
diff --git a/test/ConversionTest.java b/test/ConversionTest.java
new file mode 100644 (file)
index 0000000..2b22364
--- /dev/null
@@ -0,0 +1,294 @@
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.Main;
+import be.nikiroo.fanfix.output.BasicOutput;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.TraceHandler;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class ConversionTest extends TestLauncher {
+       private class MainWithConvert extends Main {
+               @Override
+               public int convert(String urlString, OutputType type, String target,
+                               boolean infoCover, Progress pg) {
+                       return super.convert(urlString, type, target, infoCover, pg);
+               }
+       }
+       
+       private String testUri;
+       private String expectedDir;
+       private String resultDir;
+       private List<BasicOutput.OutputType> realTypes;
+       private Map<String, List<String>> skipCompare;
+       private Map<String, List<String>> skipCompareCross;
+
+       public ConversionTest(String testName, final String testUri,
+                       final String expectedDir, final String resultDir, String[] args) {
+               super("Conversion - " + testName, args);
+
+               this.testUri = testUri;
+               this.expectedDir = expectedDir;
+               this.resultDir = resultDir;
+
+               // Special mode SYSOUT is not a file type (System.out)
+               realTypes = new ArrayList<BasicOutput.OutputType>();
+               for (BasicOutput.OutputType type : BasicOutput.OutputType.values()) {
+                       if (!BasicOutput.OutputType.SYSOUT.equals(type)) {
+                               realTypes.add(type);
+                       }
+               }
+
+               if (!testUri.startsWith("http://") && !testUri.startsWith("https://")) {
+                       addTest(new TestCase("Read the test file") {
+                               @Override
+                               public void test() throws Exception {
+                                       assertEquals("The test file \"" + testUri
+                                                       + "\" cannot be found", true,
+                                                       new File(testUri).exists());
+                               }
+                       });
+               }
+
+               addTest(new TestCase("Assure directories exist") {
+                       @Override
+                       public void test() throws Exception {
+                               new File(expectedDir).mkdirs();
+                               new File(resultDir).mkdirs();
+                               assertEquals("The Expected directory \"" + expectedDir
+                                               + "\" cannot be created", true,
+                                               new File(expectedDir).exists());
+                               assertEquals("The Result directory \"" + resultDir
+                                               + "\" cannot be created", true,
+                                               new File(resultDir).exists());
+                       }
+               });
+
+               for (BasicOutput.OutputType type : realTypes) {
+                       addTest(getTestFor(type));
+               }
+       }
+
+       @Override
+       protected void start() throws Exception {
+               skipCompare = new HashMap<String, List<String>>();
+               skipCompareCross = new HashMap<String, List<String>>();
+
+               skipCompare.put("epb.ncx", Arrays.asList(
+                               "               <meta name=\"dtb:uid\" content=",
+                               "               <meta name=\"epub-creator\" content=\""));
+               skipCompare.put("epb.opf", Arrays.asList("      <dc:subject>",
+                               "      <dc:identifier id=\"BookId\" opf:scheme=\"URI\">"));
+               skipCompare.put(".info", Arrays.asList("CREATION_DATE=",
+                               "URL=\"file:/", "UUID=EPUBCREATOR=\"", ""));
+               skipCompare.put("URL", Arrays.asList("file:/"));
+
+               for (String key : skipCompare.keySet()) {
+                       skipCompareCross.put(key, skipCompare.get(key));
+               }
+
+               skipCompareCross.put(".info", Arrays.asList(""));
+               skipCompareCross.put("epb.opf", Arrays.asList("      <dc:"));
+               skipCompareCross.put("title.xhtml",
+                               Arrays.asList("                 <div class=\"type\">"));
+               skipCompareCross.put("index.html",
+                               Arrays.asList("                 <div class=\"type\">"));
+               skipCompareCross.put("URL", Arrays.asList(""));
+       }
+
+       @Override
+       protected void stop() throws Exception {
+       }
+
+       private TestCase getTestFor(final BasicOutput.OutputType type) {
+               return new TestCase(type + " output mode") {
+                       @Override
+                       public void test() throws Exception {
+                               File target = generate(this, testUri, new File(resultDir), type);
+                               target = new File(target.getAbsolutePath()
+                                               + type.getDefaultExtension(false));
+
+                               // Check conversion:
+                               compareFiles(this, new File(expectedDir), new File(resultDir),
+                                               type, "Generate " + type);
+
+                               // LATEX not supported as input
+                               if (BasicOutput.OutputType.LATEX.equals(type)) {
+                                       return;
+                               }
+
+                               // Cross-checks:
+                               for (BasicOutput.OutputType crossType : realTypes) {
+                                       File crossDir = Test.tempFiles
+                                                       .createTempDir("cross-result");
+
+                                       generate(this, target.getAbsolutePath(), crossDir,
+                                                       crossType);
+                                       compareFiles(this, new File(resultDir), crossDir,
+                                                       crossType, "Cross compare " + crossType
+                                                                       + " generated from " + type);
+                               }
+                       }
+               };
+       }
+
+       private File generate(TestCase testCase, String testUri, File resultDir,
+                       BasicOutput.OutputType type) throws Exception {
+               final List<String> errors = new ArrayList<String>();
+
+               TraceHandler previousTraceHandler = Instance.getInstance().getTraceHandler();
+               Instance.getInstance().setTraceHandler(new TraceHandler(true, true, 0) {
+                       @Override
+                       public void error(String message) {
+                               errors.add(message);
+                       }
+
+                       @Override
+                       public void error(Exception e) {
+                               error(" ");
+                               for (Throwable t = e; t != null; t = t.getCause()) {
+                                       error(((t == e) ? "(" : "..caused by: (")
+                                                       + t.getClass().getSimpleName() + ") "
+                                                       + t.getMessage());
+                                       for (StackTraceElement s : t.getStackTrace()) {
+                                               error("\t" + s.toString());
+                                       }
+                               }
+                       }
+               });
+
+               try {
+                       File target = new File(resultDir, type.toString());
+                       int code = new MainWithConvert().convert(testUri, type,
+                                       target.getAbsolutePath(), false, null);
+
+                       String error = "";
+                       for (String err : errors) {
+                               if (!error.isEmpty())
+                                       error += "\n";
+                               error += err;
+                       }
+                       testCase.assertEquals("The conversion returned an error message: "
+                                       + error, 0, errors.size());
+                       if (code != 0) {
+                               testCase.fail("The conversion failed with return code: " + code);
+                       }
+
+                       return target;
+               } finally {
+                       Instance.getInstance().setTraceHandler(previousTraceHandler);
+               }
+       }
+
+       private void compareFiles(TestCase testCase, File expectedDir,
+                       File resultDir, final BasicOutput.OutputType limitTiFiles,
+                       final String errMess) throws Exception {
+
+               Map<String, List<String>> skipCompare = errMess.startsWith("Cross") ? this.skipCompareCross
+                               : this.skipCompare;
+
+               FilenameFilter filter = null;
+               if (limitTiFiles != null) {
+                       filter = new FilenameFilter() {
+                               @Override
+                               public boolean accept(File dir, String name) {
+                                       return name.toLowerCase().startsWith(
+                                                       limitTiFiles.toString().toLowerCase());
+                               }
+                       };
+               }
+
+               List<String> resultFiles;
+               List<String> expectedFiles;
+               {
+                       String[] resultArr = resultDir.list(filter);
+                       Arrays.sort(resultArr);
+                       resultFiles = Arrays.asList(resultArr);
+                       String[] expectedArr = expectedDir.list(filter);
+                       Arrays.sort(expectedArr);
+                       expectedFiles = Arrays.asList(expectedArr);
+               }
+
+               testCase.assertEquals(errMess, expectedFiles, resultFiles);
+
+               for (int i = 0; i < resultFiles.size(); i++) {
+                       File expected = new File(expectedDir, expectedFiles.get(i));
+                       File result = new File(resultDir, resultFiles.get(i));
+
+                       testCase.assertEquals(errMess + ": type mismatch: expected a "
+                                       + (expected.isDirectory() ? "directory" : "file")
+                                       + ", received a "
+                                       + (result.isDirectory() ? "directory" : "file"),
+                                       expected.isDirectory(), result.isDirectory());
+
+                       if (expected.isDirectory()) {
+                               compareFiles(testCase, expected, result, null, errMess);
+                               continue;
+                       }
+
+                       if (expected.getName().endsWith(".cbz")
+                                       || expected.getName().endsWith(".epub")) {
+                               File tmpExpected = Test.tempFiles.createTempDir(expected
+                                               .getName() + "[zip-content]");
+                               File tmpResult = Test.tempFiles.createTempDir(result.getName()
+                                               + "[zip-content]");
+                               IOUtils.unzip(expected, tmpExpected);
+                               IOUtils.unzip(result, tmpResult);
+                               compareFiles(testCase, tmpExpected, tmpResult, null, errMess);
+                       } else {
+                               List<String> expectedLines = Arrays.asList(IOUtils
+                                               .readSmallFile(expected).split("\n"));
+                               List<String> resultLines = Arrays.asList(IOUtils.readSmallFile(
+                                               result).split("\n"));
+
+                               String name = expected.getAbsolutePath();
+                               if (name.startsWith(expectedDir.getAbsolutePath())) {
+                                       name = expectedDir.getName()
+                                                       + name.substring(expectedDir.getAbsolutePath()
+                                                                       .length());
+                               }
+
+                               testCase.assertEquals(errMess + ": " + name
+                                               + ": the number of lines is not the same",
+                                               expectedLines.size(), resultLines.size());
+
+                               for (int j = 0; j < expectedLines.size(); j++) {
+                                       String expectedLine = expectedLines.get(j);
+                                       String resultLine = resultLines.get(j);
+
+                                       boolean skip = false;
+                                       for (Entry<String, List<String>> skipThose : skipCompare
+                                                       .entrySet()) {
+                                               for (String skipStart : skipThose.getValue()) {
+                                                       if (name.endsWith(skipThose.getKey())
+                                                                       && expectedLine.startsWith(skipStart)
+                                                                       && resultLine.startsWith(skipStart)) {
+                                                               skip = true;
+                                                       }
+                                               }
+                                       }
+
+                                       if (skip) {
+                                               continue;
+                                       }
+
+                                       testCase.assertEquals(errMess + ": line " + (j + 1)
+                                                       + " is not the same in file " + name, expectedLine,
+                                                       resultLine);
+                               }
+                       }
+               }
+       }
+}
diff --git a/test/LibraryTest.java b/test/LibraryTest.java
new file mode 100644 (file)
index 0000000..da44438
--- /dev/null
@@ -0,0 +1,258 @@
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Paragraph;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.library.BasicLibrary;
+import be.nikiroo.fanfix.library.LocalLibrary;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class LibraryTest extends TestLauncher {
+       private BasicLibrary lib;
+       private File tmp;
+
+       public LibraryTest(String[] args) {
+               super("Library", args);
+
+               final String luid1 = "001"; // A
+               final String luid2 = "002"; // B
+               final String luid3 = "003"; // B then A, then B
+               final String name1 = "My story 1";
+               final String name2 = "My story 2";
+               final String name3 = "My story 3";
+               final String name3ex = "My story 3 [edited]";
+               final String source1 = "Source A";
+               final String source2 = "Source B";
+               final String author1 = "Unknown author";
+               final String author2 = "Another other otter author";
+
+               final String errMess = "The resulting stories in the list are not what we expected";
+
+               addSeries(new TestLauncher("Local", args) {
+                       {
+                               addTest(new TestCase("getList") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = lib.getList().getMetas();
+                                               assertEquals(errMess, Arrays.asList(),
+                                                               titlesAsList(metas));
+                                       }
+                               });
+
+                               addTest(new TestCase("save") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               lib.save(story(luid1, name1, source1, author1), luid1,
+                                                               null);
+
+                                               List<MetaData> metas = lib.getList().getMetas();
+                                               assertEquals(errMess, Arrays.asList(name1),
+                                                               titlesAsList(metas));
+                                       }
+                               });
+
+                               addTest(new TestCase("save more") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = null;
+
+                                               lib.save(story(luid2, name2, source2, author1), luid2,
+                                                               null);
+
+                                               metas = lib.getList().getMetas();
+                                               assertEquals(errMess, Arrays.asList(name1, name2),
+                                                               titlesAsList(metas));
+
+                                               lib.save(story(luid3, name3, source2, author1), luid3,
+                                                               null);
+
+                                               metas = lib.getList().getMetas();
+                                               assertEquals(errMess,
+                                                               Arrays.asList(name1, name2, name3),
+                                                               titlesAsList(metas));
+                                       }
+                               });
+
+                               addTest(new TestCase("save override luid (change author)") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               // same luid as a previous one
+                                               lib.save(story(luid3, name3ex, source2, author2),
+                                                               luid3, null);
+
+                                               List<MetaData> metas = lib.getList().getMetas();
+                                               assertEquals(errMess,
+                                                               Arrays.asList(name1, name2, name3ex),
+                                                               titlesAsList(metas));
+                                       }
+                               });
+
+                               addTest(new TestCase("getList with results") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = lib.getList().getMetas();
+                                               assertEquals(3, metas.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("getList by source") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = null;
+
+                                               metas = lib.getList().filter(source1, null, null);
+                                               assertEquals(1, metas.size());
+
+                                               metas = lib.getList().filter(source2, null, null);
+                                               assertEquals(2, metas.size());
+
+                                               metas = lib.getList().filter((String)null, null, null);
+                                               assertEquals(3, metas.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("getList by author") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = null;
+
+                                               metas = lib.getList().filter(null, author1, null);
+                                               assertEquals(2, metas.size());
+
+                                               metas = lib.getList().filter(null, author2, null);
+                                               assertEquals(1, metas.size());
+
+                                               metas = lib.getList().filter((String)null, null, null);
+                                               assertEquals(3, metas.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("changeType") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = null;
+
+                                               lib.changeSource(luid3, source1, null);
+
+                                               metas = lib.getList().filter(source1, null, null);
+                                               assertEquals(2, metas.size());
+
+                                               metas = lib.getList().filter(source2, null, null);
+                                               assertEquals(1, metas.size());
+
+                                               metas = lib.getList().filter((String)null, null, null);
+                                               assertEquals(3, metas.size());
+                                       }
+                               });
+
+                               addTest(new TestCase("save override luid (change source)") {
+                                       @Override
+                                       public void test() throws Exception {
+                                               List<MetaData> metas = null;
+
+                                               // same luid as a previous one
+                                               lib.save(story(luid3, "My story 3", source2, author2),
+                                                               luid3, null);
+
+                                               metas = lib.getList().filter(source1, null, null);
+                                               assertEquals(1, metas.size());
+
+                                               metas = lib.getList().filter(source2, null, null);
+                                               assertEquals(2, metas.size());
+
+                                               metas = lib.getList().filter((String)null, null, null);
+                                               assertEquals(3, metas.size());
+                                       }
+                               });
+                       }
+               });
+       }
+
+       @Override
+       protected void start() throws Exception {
+               tmp = File.createTempFile(".test-fanfix", ".library");
+               tmp.delete();
+               tmp.mkdir();
+
+               lib = new LocalLibrary(tmp, OutputType.INFO_TEXT, OutputType.CBZ);
+       }
+
+       @Override
+       protected void stop() throws Exception {
+               IOUtils.deltree(tmp);
+       }
+
+       /**
+        * Return the (sorted) list of titles present in this list of
+        * {@link MetaData}s.
+        * 
+        * @param metas
+        *            the meta
+        * 
+        * @return the sorted list
+        */
+       private List<String> titlesAsList(List<MetaData> metas) {
+               List<String> list = new ArrayList<String>();
+               for (MetaData meta : metas) {
+                       list.add(meta.getTitle());
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       private Story story(String luid, String title, String source, String author) {
+               Story story = new Story();
+
+               MetaData meta = new MetaData();
+               meta.setLuid(luid);
+               meta.setTitle(title);
+               meta.setSource(source);
+               meta.setAuthor(author);
+               story.setMeta(meta);
+
+               Chapter resume = chapter(0, "Resume");
+               meta.setResume(resume);
+
+               List<Chapter> chapters = new ArrayList<Chapter>();
+               chapters.add(chapter(1, "Chap 1"));
+               chapters.add(chapter(2, "Chap 2"));
+               story.setChapters(chapters);
+
+               long words = 0;
+               for (Chapter chap : story.getChapters()) {
+                       words += chap.getWords();
+               }
+               meta.setWords(words);
+
+               return story;
+       }
+
+       private Chapter chapter(int number, String name) {
+               Chapter chapter = new Chapter(number, name);
+
+               List<Paragraph> paragraphs = new ArrayList<Paragraph>();
+               paragraphs.add(new Paragraph(Paragraph.ParagraphType.NORMAL,
+                               "some words in this paragraph please thank you", 8));
+
+               chapter.setParagraphs(paragraphs);
+
+               long words = 0;
+               for (Paragraph para : chapter.getParagraphs()) {
+                       words += para.getWords();
+               }
+               chapter.setWords(words);
+
+               return chapter;
+       }
+}
diff --git a/test/Test.java b/test/Test.java
new file mode 100644 (file)
index 0000000..5ec24a4
--- /dev/null
@@ -0,0 +1,177 @@
+package be.nikiroo.fanfix.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.Config;
+import be.nikiroo.fanfix.bundles.ConfigBundle;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.TempFiles;
+import be.nikiroo.utils.resources.Bundles;
+import be.nikiroo.utils.test.TestLauncher;
+
+/**
+ * Tests for Fanfix.
+ * 
+ * @author niki
+ */
+public class Test extends TestLauncher {
+       //
+       // 4 files can control the test:
+       // - test/VERBOSE: enable verbose mode
+       // - test/OFFLINE: to forbid any downloading
+       // - test/URLS: to allow testing URLs
+       // - test/FORCE_REFRESH: to force a clear of the cache
+       //
+       // Note that test/CACHE can be kept, as it will contain all internet related
+       // files you need (if you allow URLs, run the test once which will populate
+       // the CACHE then go OFFLINE, it will still work).
+       //
+       // The test files will be:
+       // - test/*.url: URL to download in text format, content = URL
+       // - test/*.story: text mode story, content = story
+       //
+
+       /**
+        * The temporary files handler.
+        */
+       static TempFiles tempFiles;
+
+       /**
+        * Create the Fanfix {@link TestLauncher}.
+        * 
+        * @param args
+        *            the arguments to configure the number of columns and the ok/ko
+        *            {@link String}s
+        * @param urlsAllowed
+        *            allow testing URLs (<tt>.url</tt> files)
+        * 
+        * @throws IOException
+        */
+       public Test(String[] args, boolean urlsAllowed) throws IOException {
+               super("Fanfix", args);
+               Instance.getInstance().setTraceHandler(null);
+               addSeries(new BasicSupportUtilitiesTest(args));
+               addSeries(new BasicSupportDeprecatedTest(args));
+               addSeries(new LibraryTest(args));
+
+               File sources = new File("test/");
+               if (sources.isDirectory()) {
+                       for (File file : sources.listFiles()) {
+                               if (file.isDirectory()) {
+                                       continue;
+                               }
+
+                               String expectedDir = new File(file.getParentFile(), "expected_"
+                                               + file.getName()).getAbsolutePath();
+                               String resultDir = new File(file.getParentFile(), "result_"
+                                               + file.getName()).getAbsolutePath();
+
+                               String uri;
+                               if (urlsAllowed && file.getName().endsWith(".url")) {
+                                       uri = IOUtils.readSmallFile(file).trim();
+                               } else if (file.getName().endsWith(".story")) {
+                                       uri = file.getAbsolutePath();
+                               } else {
+                                       continue;
+                               }
+
+                               addSeries(new ConversionTest(file.getName(), uri, expectedDir,
+                                               resultDir, args));
+                       }
+               }
+       }
+
+       /**
+        * Main entry point of the program.
+        * 
+        * @param args
+        *            the arguments passed to the {@link TestLauncher}s.
+        * @throws IOException
+        *             in case of I/O error
+        */
+       static public void main(String[] args) throws IOException {
+               Instance.init();
+
+               // Verbose mode:
+               boolean verbose = new File("test/VERBOSE").exists();
+
+               // Can force refresh
+               boolean forceRefresh = new File("test/FORCE_REFRESH").exists();
+
+               // Allow URLs:
+               boolean urlsAllowed = new File("test/URLS").exists();
+
+               
+               // Only download files if allowed:
+               boolean offline = new File("test/OFFLINE").exists();
+               Instance.getInstance().getCache().setOffline(offline);
+
+
+               
+               int result = 0;
+               tempFiles = new TempFiles("fanfix-test");
+               try {
+                       File tmpConfig = tempFiles.createTempDir("fanfix-config");
+                       File localCache = new File("test/CACHE");
+                       prepareCache(localCache, forceRefresh);
+
+                       ConfigBundle config = new ConfigBundle();
+                       Bundles.setDirectory(tmpConfig.getAbsolutePath());
+                       config.setString(Config.CACHE_DIR, localCache.getAbsolutePath());
+                       config.setInteger(Config.CACHE_MAX_TIME_STABLE, -1);
+                       config.setInteger(Config.CACHE_MAX_TIME_CHANGING, -1);
+                       config.updateFile(tmpConfig.getPath());
+                       System.setProperty("CONFIG_DIR", tmpConfig.getAbsolutePath());
+
+                       Instance.init(true);
+                       Instance.getInstance().getCache().setOffline(offline);
+
+                       TestLauncher tests = new Test(args, urlsAllowed);
+                       tests.setDetails(verbose);
+
+                       result = tests.launch();
+
+                       IOUtils.deltree(tmpConfig);
+                       prepareCache(localCache, forceRefresh);
+               } finally {
+                       // Test temp files
+                       tempFiles.close();
+
+                       // This is usually done in Fanfix.Main:
+                       Instance.getInstance().getTempFiles().close();
+               }
+
+               System.exit(result);
+       }
+
+       /**
+        * Prepare the cache (or clean it up).
+        * <p>
+        * The cache directory will always exist if this method succeed
+        * 
+        * @param localCache
+        *            the cache directory
+        * @param forceRefresh
+        *            TRUE to force acache refresh (delete all files)
+        * 
+        * @throw IOException if the cache cannot be created
+        */
+       private static void prepareCache(File localCache, boolean forceRefresh)
+                       throws IOException {
+               // if needed
+               localCache.mkdirs();
+
+               if (!localCache.isDirectory()) {
+                       throw new IOException("Cannot get a cache");
+               }
+
+               // delete local cached files (_*) or all files if forceRefresh
+               for (File f : localCache.listFiles()) {
+                       if (forceRefresh || f.getName().startsWith("_")) {
+                               IOUtils.deltree(f);
+                       }
+               }
+       }
+}
diff --git a/test/expected_test.story/cbz.cbz b/test/expected_test.story/cbz.cbz
new file mode 100644 (file)
index 0000000..28cc25b
Binary files /dev/null and b/test/expected_test.story/cbz.cbz differ
diff --git a/test/expected_test.story/epub.epub b/test/expected_test.story/epub.epub
new file mode 100644 (file)
index 0000000..776d05d
Binary files /dev/null and b/test/expected_test.story/epub.epub differ
diff --git a/test/expected_test.story/html/html.info b/test/expected_test.story/html/html.info
new file mode 100644 (file)
index 0000000..506e477
--- /dev/null
@@ -0,0 +1,18 @@
+TITLE="The trials of Fanfan"
+AUTHOR="UnknownArtist366"
+DATE="2018"
+SUBJECT="test"
+SOURCE="text"
+URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+TAGS=""
+UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+LUID=""
+LANG="en"
+IMAGES_DOCUMENT="false"
+TYPE="html"
+COVER=""
+EPUBCREATOR="Fanfix (by Niki)"
+PUBLISHER=""
+WORDCOUNT="57"
+CREATION_DATE="2018-03-28 08:40:18"
+FAKE_COVER="false"
diff --git a/test/expected_test.story/html/html.txt b/test/expected_test.story/html/html.txt
new file mode 100644 (file)
index 0000000..a3a3af3
--- /dev/null
@@ -0,0 +1,27 @@
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+——————————————————————
+
+This ‘story’ is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+—————————————————
+
+“Yes, quotes!”, I said.
+‘Those can start with a single- or double quote sign’, I continued.
+“They also supports other characters, and an optionnal leading dash.”
+“The optionnal leading dash is enough to signify “quote”.”
+
+Chapter 2: “Quote” test
+———————————————————————
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
diff --git a/test/expected_test.story/html/index.html b/test/expected_test.story/html/index.html
new file mode 100644 (file)
index 0000000..aade9c3
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+       <meta http-equiv='content-type' content='text/html; charset=utf-8'>
+       <meta name='viewport' content='width=device-width, initial-scale=1.0'>
+       <link rel='stylesheet' type='text/css' href='style.css'>
+       <title>The trials of Fanfan</title>
+</head>
+<body>
+
+       <div class="titlepage">
+               <h1>The trials of Fanfan</h1>
+                       <div class="type"></div>
+               <div class="cover">
+                       <img src="cover.png"></img>
+               </div>
+               <div class="author">UnknownArtist366</div>
+       </div>
+       <hr/><br/>              <div class='normals'>
+               <span class='normal'>This ‘story’ is nothing more than a test file to check</span>
+               <span class='normal'>that the program can convert it into different</span>
+               <span class='normal'>formats correctly.</span>
+               </div>
+
+       <br/>
+       <h2>
+               <span class='chap'>Chapter <span class='chapnumber'>1</span>:</span> 
+               <span class='chaptitle'>Quotes</span>
+       </h2>
+       
+       <div class='chapter_content'>
+               <div class='dialogues'>
+                       <div class='dialogue'>&mdash; “Yes, quotes!”, I said.</div>
+                       <div class='dialogue'>&mdash; ‘Those can start with a single- or double quote sign’, I continued.</div>
+                       <div class='dialogue'>&mdash; “They also supports other characters, and an optionnal leading dash.”</div>
+                       <div class='dialogue'>&mdash; “The optionnal leading dash is enough to signify “quote”.”</div>
+               </div>
+
+       </div>
+       <h2>
+               <span class='chap'>Chapter <span class='chapnumber'>2</span>:</span> 
+               <span class='chaptitle'>“Quote” test</span>
+       </h2>
+       
+       <div class='chapter_content'>
+               <div class='normals'>
+               <span class='normal'>This test was just for the chapter title.</span>
+               <span class='normal'>We can also check for breaks.</span>
+               </div>
+               <hr class='break'/>
+               <div class='normals'>
+               <span class='normal'>This was a break space.</span>
+               </div>
+
+       </div></body>
diff --git a/test/expected_test.story/html/style.css b/test/expected_test.story/html/style.css
new file mode 100644 (file)
index 0000000..6b6d0d2
--- /dev/null
@@ -0,0 +1,112 @@
+html {
+       text-align: justify;
+       max-width: 800px;
+       margin: auto;
+}
+
+.titlepage {
+       padding-left: 10%;
+       padding-right: 10%;
+       width: 80%;
+}
+
+h1 {
+       padding-bottom: 0;
+       margin-bottom: 0;
+       text-align: left;
+}
+
+.type {
+       position: relative;
+       font-size: large;
+       color: #666666;
+       font-weight: bold;
+       padding-bottom: 10px;
+       text-align: left;
+}
+
+.cover, .page-image {
+       width: 100%;
+}
+
+.cover img {
+       height: 45%;
+       max-width: 100%;
+       margin: auto;
+}
+
+.author {
+       text-align: right;
+       font-size: large;
+       font-style: italic;
+}
+
+.book, .chapter_content {
+       NO_text-indent: 40px;
+       padding-top: 40px;
+       padding-left: 5%;
+       padding-right: 5%;
+       width: 90%;
+}
+
+h2 {
+       border: 1px solid black;
+       color: #222222;
+       padding-left: 10px;
+       padding-right: 10px;
+       display: block;
+       padding-bottom: 0;
+       margin-bottom: 0;
+}
+
+h2 .chap {
+       color: #000000;
+       font-size: large;
+       font-variant: small-caps;
+       display: block;
+}
+
+h2 .chap:first-letter {
+       font-weight: bold;
+}
+
+h2 .chapnumber {
+       color: #000000;
+       font-size: xx-large;
+}
+
+h2 .chaptitle {
+       color: #444444;
+       font-size: large;
+       font-style: italic;
+       padding-bottom: 5px;
+       text-align: right;
+       display: block;
+}
+
+.normals {
+}
+
+.normal {
+       /* Can be removed if you want a more "compact" view */
+       display: block;
+}
+
+.blank {
+       /* Can be removed if you want a more "compact" view */
+       height: 24px;
+       width: 100%;
+}
+
+hr.break {
+       /* Can be removed if you want a more "compact" view */
+       margin-top: 48px;
+       margin-bottom: 48px;
+}
+
+.dialogues {
+}
+
+.dialogue {
+       font-style: italic;
+}
diff --git a/test/expected_test.story/info_text b/test/expected_test.story/info_text
new file mode 100644 (file)
index 0000000..768b3cc
--- /dev/null
@@ -0,0 +1,24 @@
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+
+This 'story' is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+
+"Yes, quotes!", I said.
+'Those can start with a single- or double quote sign', I continued.
+"They also supports other characters, and an optionnal leading dash."
+"The optionnal leading dash is enough to signify "quote"."
+
+Chapter 2: “Quote” test
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
diff --git a/test/expected_test.story/info_text.info b/test/expected_test.story/info_text.info
new file mode 100644 (file)
index 0000000..cc522dd
--- /dev/null
@@ -0,0 +1,18 @@
+TITLE="The trials of Fanfan"
+AUTHOR="UnknownArtist366"
+DATE="2018"
+SUBJECT="test"
+SOURCE="text"
+URL="file:/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+TAGS=""
+UUID="/media/xubuntu/sd32/workspace/fanfix/test/test.story"
+LUID=""
+LANG="en"
+IMAGES_DOCUMENT="false"
+TYPE="info_text"
+COVER=""
+EPUBCREATOR="Fanfix (by Niki)"
+PUBLISHER=""
+WORDCOUNT="57"
+CREATION_DATE="2018-03-28 08:39:39"
+FAKE_COVER="false"
diff --git a/test/expected_test.story/latex.tex b/test/expected_test.story/latex.tex
new file mode 100644 (file)
index 0000000..d4dd68f
--- /dev/null
@@ -0,0 +1,44 @@
+%
+% This LaTeX document was auto-generated by Fanfic Reader, created by Niki.
+%
+
+\documentclass[a4paper]{book}
+\usepackage[english]{babel}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{lmodern}
+\newcommand{\br}{\vspace{10 mm}}
+\newcommand{\say}{--- \noindent\emph}
+\hyphenpenalty=1000
+\tolerance=5000
+\begin{document}
+\date{2018}
+\title{The trials of Fanfan}
+\author{UnknownArtist366}
+\maketitle
+
+
+
+\chapter{Quotes}
+
+\say{``Yes, quotes!'', I said.}
+
+\say{`Those can start with a single- or double quote sign', I continued.}
+
+\noindent{}
+\say{``They also supports other characters, and an optionnal leading dash.''}
+
+\noindent{}
+\say{``The optionnal leading dash is enough to signify ``quote''.''}
+
+\noindent{}
+
+
+\chapter{``Quote'' test}
+This test was just for the chapter title.
+We can also check for breaks.
+
+\br
+This was a break space.
+
+\end{document}
diff --git a/test/expected_test.story/text.txt b/test/expected_test.story/text.txt
new file mode 100644 (file)
index 0000000..a3a3af3
--- /dev/null
@@ -0,0 +1,27 @@
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+——————————————————————
+
+This ‘story’ is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+—————————————————
+
+“Yes, quotes!”, I said.
+‘Those can start with a single- or double quote sign’, I continued.
+“They also supports other characters, and an optionnal leading dash.”
+“The optionnal leading dash is enough to signify “quote”.”
+
+Chapter 2: “Quote” test
+———————————————————————
+
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
diff --git a/test/test.story b/test/test.story
new file mode 100644 (file)
index 0000000..7365b60
--- /dev/null
@@ -0,0 +1,23 @@
+The trials of Fanfan
+by UnknownArtist366 (2018)
+
+Chapter 0: Description
+
+This 'story' is nothing more than a test file to check
+that the program can convert it into different
+formats correctly.
+
+Chapter 1: Quotes
+- "Yes, quotes!", I said.
+'Those can start with a single- or double quote sign', I continued.
+- «They also supports other characters, and an optionnal leading dash.»
+- The optionnal leading dash is enough to signify "quote".
+
+Chapter 2: "Quote" test
+This test was just for the chapter title.
+We can also check for breaks.
+
+* * *
+
+This was a break space.
+