weblib: full r/w support
authorNiki Roo <niki@nikiroo.be>
Thu, 14 May 2020 10:05:30 +0000 (12:05 +0200)
committerNiki Roo <niki@nikiroo.be>
Thu, 14 May 2020 10:05:30 +0000 (12:05 +0200)
src/be/nikiroo/fanfix/data/JsonIO.java
src/be/nikiroo/fanfix/library/BasicLibrary.java
src/be/nikiroo/fanfix/library/CacheLibrary.java
src/be/nikiroo/fanfix/library/LocalLibrary.java
src/be/nikiroo/fanfix/library/RemoteLibrary.java
src/be/nikiroo/fanfix/library/WebLibrary.java
src/be/nikiroo/fanfix/library/WebLibraryServer.java
src/be/nikiroo/fanfix/library/WebLibraryServerHtml.java
src/be/nikiroo/fanfix/library/WebLibraryUrls.java

index 5157dca3156a2a88eada47cabaa080191d6bb2c1..beff342bea00abd1d002c00a09716438a667a182 100644 (file)
@@ -8,6 +8,7 @@ import org.json.JSONException;
 import org.json.JSONObject;
 
 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
+import be.nikiroo.utils.Progress;
 
 public class JsonIO {
        static public JSONObject toJson(MetaData meta) {
@@ -200,6 +201,36 @@ public class JsonIO {
                return para;
        }
 
+       // no children included
+       static public JSONObject toJson(Progress pg) {
+               if (pg == null) {
+                       return null;
+               }
+
+               JSONObject json = new JSONObject();
+
+               put(json, "", Progress.class.getName());
+               put(json, "name", pg.getName());
+               put(json, "min", pg.getMin());
+               put(json, "max", pg.getMax());
+               put(json, "progress", pg.getProgress());
+
+               return json;
+       }
+
+       // no children included
+       static public Progress toProgress(JSONObject json) {
+               if (json == null) {
+                       return null;
+               }
+
+               Progress pg = new Progress(getString(json, "name"),
+                               getInt(json, "min", 0), getInt(json, "max", 100));
+               pg.setProgress(getInt(json, "progress", 0));
+
+               return pg;
+       }
+
        static public List<String> toListString(JSONArray array) {
                if (array != null) {
                        List<String> values = new ArrayList<String>();
index af7920bf14efdde77dbd2bebe77489f505b6fa30..f6024eef85025eefc0f0df1ada0b02fd2c2b254a 100644 (file)
@@ -311,7 +311,7 @@ abstract public class BasicLibrary {
         * 
         * @return the next luid
         */
-       protected abstract int getNextId();
+       protected abstract String getNextId();
 
        /**
         * Delete the target {@link Story}.
@@ -741,7 +741,7 @@ abstract public class BasicLibrary {
                pg.setName("Saving story");
 
                if (luid == null || luid.isEmpty()) {
-                       meta.setLuid(String.format("%03d", getNextId()));
+                       meta.setLuid(getNextId());
                } else {
                        meta.setLuid(luid);
                }
index a3c3b5e3b7bb6f5a61962e8837158e28692feb56..e184c1bd6555e7d79547ab61ab774d92e5b608ff 100644 (file)
@@ -419,7 +419,7 @@ public class CacheLibrary extends BasicLibrary {
        // BasicLibrary:
 
        @Override
-       protected int getNextId() {
+       protected String getNextId() {
                throw new java.lang.InternalError("Should not have been called");
        }
 
index 6720972682939785226de86a201ef2c4c4c7d68f..7220a3951de137f99a4a11ecc7e5f5b119abb3c1 100644 (file)
@@ -20,10 +20,10 @@ import be.nikiroo.fanfix.output.BasicOutput;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.fanfix.output.InfoCover;
 import be.nikiroo.fanfix.supported.InfoReader;
+import be.nikiroo.utils.HashUtils;
 import be.nikiroo.utils.IOUtils;
 import be.nikiroo.utils.Image;
 import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.StringUtils;
 
 /**
  * This {@link BasicLibrary} will store the stories locally on disk.
@@ -168,11 +168,11 @@ public class LocalLibrary extends BasicLibrary {
        }
 
        @Override
-       protected int getNextId() {
+       protected String getNextId() {
                getStories(null); // make sure lastId is set
 
                synchronized (lock) {
-                       return ++lastId;
+                       return String.format("%03d", ++lastId);
                }
        }
 
@@ -553,7 +553,7 @@ public class LocalLibrary extends BasicLibrary {
         */
        private File getAuthorCoverFile(String author) {
                File aDir = new File(baseDir, "_AUTHORS");
-               String hash = StringUtils.getMd5Hash(author);
+               String hash = HashUtils.md5(author);
                String ext = Instance.getInstance().getConfig()
                                .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER);
                return new File(aDir, hash + "." + ext.toLowerCase());
index 3a60e02c9ed6fc845193574f897c3bd9b0ecc60a..ad92800878f63e31587fa449a07c4618a971bc49 100644 (file)
@@ -485,7 +485,7 @@ public class RemoteLibrary extends BasicLibrary {
        // The following methods are only used by Save and Delete in BasicLibrary:
 
        @Override
-       protected int getNextId() {
+       protected String getNextId() {
                throw new java.lang.InternalError("Should not have been called");
        }
 
index 040acd07f1505acb49845d4ad153a3661dbae8b6..9d1c773aae698457d920b5094668eebb74605202 100644 (file)
@@ -103,11 +103,15 @@ public class WebLibrary extends BasicLibrary {
 
                this.host = host;
                this.port = port;
-
-               // TODO: not supported yet
-               this.rw = false;
        }
 
+       /**
+        * Return the version of the program running server-side.
+        * <p>
+        * Never returns NULL.
+        * 
+        * @return the version or an empty {@link Version} if not known
+        */
        public Version getVersion() {
                try {
                        InputStream in = post(WebLibraryUrls.VERSION_URL);
@@ -191,8 +195,9 @@ public class WebLibrary extends BasicLibrary {
        @Override
        public synchronized Story getStory(final String luid, Progress pg)
                        throws IOException {
-
-               // TODO: pg
+               if (pg == null) {
+                       pg = new Progress();
+               }
 
                Story story;
                InputStream in = post(WebLibraryUrls.getStoryUrlJson(luid));
@@ -203,6 +208,12 @@ public class WebLibrary extends BasicLibrary {
                        in.close();
                }
 
+               int max = 0;
+               for (Chapter chap : story) {
+                       max += chap.getParagraphs().size();
+               }
+               pg.setMinMax(0, max);
+
                story.getMeta().setCover(getCover(luid));
                int chapNum = 1;
                for (Chapter chap : story) {
@@ -218,12 +229,14 @@ public class WebLibrary extends BasicLibrary {
                                        }
                                }
 
+                               pg.add(1);
                                number++;
                        }
 
                        chapNum++;
                }
 
+               pg.done();
                return story;
        }
 
@@ -243,8 +256,9 @@ public class WebLibrary extends BasicLibrary {
        @Override
        // Could work (more slowly) without it
        public MetaData imprt(final URL url, Progress pg) throws IOException {
-               if (true)
-                       throw new IOException("Not implemented yet");
+               if (pg == null) {
+                       pg = new Progress();
+               }
 
                // Import the file locally if it is actually a file
 
@@ -254,8 +268,44 @@ public class WebLibrary extends BasicLibrary {
 
                // Import it remotely if it is an URL
 
-               // TODO
-               return super.imprt(url, pg);
+               try {
+                       String luid = null;
+
+                       Map<String, String> post = new HashMap<String, String>();
+                       post.put("url", url.toString());
+                       InputStream in = post(WebLibraryUrls.IMPRT_URL_IMPORT, post);
+                       try {
+                               luid = IOUtils.readSmallStream(in);
+                       } finally {
+                               in.close();
+                       }
+
+                       Progress subPg = null;
+                       do {
+                               try {
+                                       Thread.sleep(2000);
+                               } catch (InterruptedException e) {
+                               }
+
+                               in = post(WebLibraryUrls.getImprtProgressUrl(luid));
+                               try {
+                                       subPg = JsonIO.toProgress(
+                                                       new JSONObject(IOUtils.readSmallStream(in)));
+                               } finally {
+                                       in.close();
+                               }
+                       } while (subPg != null);
+
+                       in = post(WebLibraryUrls.getStoryUrlMetadata(luid));
+                       try {
+                               return JsonIO.toMetaData(
+                                               new JSONObject(IOUtils.readSmallStream(in)));
+                       } finally {
+                               in.close();
+                       }
+               } finally {
+                       pg.done();
+               }
        }
 
        @Override
@@ -296,7 +346,7 @@ public class WebLibrary extends BasicLibrary {
        // The following methods are only used by Save and Delete in BasicLibrary:
 
        @Override
-       protected int getNextId() {
+       protected String getNextId() {
                throw new java.lang.InternalError("Should not have been called");
        }
 
index 55e8ed3d146658f5529471eb645d3caa26c1fe77..5cda3b5d712e6aa062ca87e9b16b4d157a40752f 100644 (file)
@@ -3,6 +3,7 @@ package be.nikiroo.fanfix.library;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -26,6 +27,7 @@ import be.nikiroo.utils.LoginResult;
 import be.nikiroo.utils.NanoHTTPD;
 import be.nikiroo.utils.NanoHTTPD.Response;
 import be.nikiroo.utils.NanoHTTPD.Response.Status;
+import be.nikiroo.utils.Progress;
 
 public class WebLibraryServer extends WebLibraryServerHtml {
        class WLoginResult extends LoginResult {
@@ -73,6 +75,8 @@ public class WebLibraryServer extends WebLibraryServerHtml {
        private List<String> whitelist;
        private List<String> blacklist;
 
+       private Map<String, Progress> imprts = new HashMap<String, Progress>();
+
        public WebLibraryServer(boolean secure) throws IOException {
                super(secure);
 
@@ -393,6 +397,64 @@ public class WebLibraryServer extends WebLibraryServerHtml {
                return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null);
        }
 
+       @Override
+       protected Response imprt(String uri, String urlStr, WLoginResult login)
+                       throws IOException {
+               final BasicLibrary lib = Instance.getInstance().getLibrary();
+
+               final URL url = new URL(urlStr);
+               final Progress pg = new Progress();
+               final String luid = lib.getNextId();
+
+               synchronized (imprts) {
+                       imprts.put(luid, pg);
+               }
+
+               new Thread(new Runnable() {
+                       @Override
+                       public void run() {
+                               try {
+                                       lib.imprt(url, pg);
+                               } catch (IOException e) {
+                                       Instance.getInstance().getTraceHandler().error(e);
+                               } finally {
+                                       synchronized (imprts) {
+                                               imprts.remove(luid);
+                                       }
+                               }
+                       }
+               }, "Import story: " + urlStr).start();
+
+               lib.imprt(url, pg);
+
+               return NanoHTTPD.newFixedLengthResponse(Status.OK,
+                               NanoHTTPD.MIME_PLAINTEXT, luid);
+       }
+
+       @Override
+       protected Response imprtProgress(String uri, WLoginResult login) {
+               String[] uriParts = uri.split("/");
+               int off = 2; // "" and "import"
+
+               if (uriParts.length < off + 1) {
+                       return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+                                       NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request");
+               }
+
+               String luid = uriParts[off + 0];
+
+               Progress pg = null;
+               synchronized (imprts) {
+                       pg = imprts.get(luid);
+               }
+               if (pg != null) {
+                       return NanoHTTPD.newFixedLengthResponse(Status.OK,
+                                       "application/json", JsonIO.toJson(pg).toString());
+               }
+
+               return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null);
+       }
+
        @Override
        protected List<MetaData> metas(WLoginResult login) throws IOException {
                BasicLibrary lib = Instance.getInstance().getLibrary();
index 77b8efede1a4eb9580219a74cf9d6aee62157469..a534b4cdafbbdd8ec1c4c6b373bb7fc2953ec58c 100644 (file)
@@ -62,6 +62,11 @@ abstract class WebLibraryServerHtml implements Runnable {
        abstract protected Story story(String luid, WLoginResult login)
                        throws IOException;
 
+       protected abstract Response imprt(String uri, String url,
+                       WLoginResult login) throws IOException;
+
+       protected abstract Response imprtProgress(String uri, WLoginResult login);
+
        public WebLibraryServerHtml(boolean secure) throws IOException {
                Integer port = Instance.getInstance().getConfig()
                                .getInteger(Config.SERVER_PORT);
@@ -193,6 +198,13 @@ abstract class WebLibraryServerHtml implements Runnable {
                                                                session.getCookies().delete("cookie");
                                                                cookies.remove("cookie");
                                                                rep = loginPage(login(false, false), uri);
+                                                       } else if (WebLibraryUrls.isImprtUrl(uri)) {
+                                                               String url = params.get("url");
+                                                               if (url != null) {
+                                                                       rep = imprt(uri, url, login);
+                                                               } else {
+                                                                       rep = imprtProgress(uri, login);
+                                                               }
                                                        } else {
                                                                getTraceHandler().error(
                                                                                "Supported URL was not processed: "
index c1d1cf21f3621ee4a89c99d2b96508fe3a3d1cea..41f1c82feb17c2312ddc3868f5fd9176f3886c59 100644 (file)
@@ -17,6 +17,8 @@ class WebLibraryUrls {
        static private final String STORY_URL_COVER = STORY_URL_BASE
                        + "{luid}/cover";
        static private final String STORY_URL_JSON = STORY_URL_BASE + "{luid}/json";
+       static private final String STORY_URL_METADATA = STORY_URL_BASE
+                       + "{luid}/metadata";
 
        // GET/SET ("value" param -> set STA to this value)
        static private final String STORY_URL_SOURCE = STORY_URL_BASE
@@ -30,6 +32,12 @@ class WebLibraryUrls {
 
        static public final String LIST_URL_METADATA = LIST_URL_BASE + "metadata";
 
+       // "import" requires param "url" and return an luid, "/{luid}" return
+       // progress status as a JSON Progress or 404 if none (done or failed)
+       static private final String IMPRT_URL_BASE = "/import/";
+       static private final String IMPRT_URL_PROGRESS = IMPRT_URL_BASE + "{luid}";
+       static public final String IMPRT_URL_IMPORT = IMPRT_URL_BASE + "import";
+
        // GET/SET ("luid" param -> set cover to the cover of this story -- not ok
        // for /cover/story/)
        static private final String COVER_URL_BASE = "/cover/";
@@ -80,10 +88,20 @@ class WebLibraryUrls {
                                .replace("{luid}", luid);
        }
 
+       static public String getStoryUrlMetadata(String luid) {
+               return STORY_URL_METADATA //
+                               .replace("{luid}", luid);
+       }
+
+       static public String getImprtProgressUrl(String luid) {
+               return IMPRT_URL_PROGRESS //
+                               .replace("{luid}", luid);
+       }
+
        static public boolean isSupportedUrl(String url) {
                return INDEX_URL.equals(url) || VERSION_URL.equals(url)
                                || LOGOUT_URL.equals(url) || isViewUrl(url) || isStoryUrl(url)
-                               || isListUrl(url) || isCoverUrl(url);
+                               || isListUrl(url) || isCoverUrl(url) || isImprtUrl(url);
        }
 
        static public String getCoverUrlStory(String luid) {
@@ -116,4 +134,8 @@ class WebLibraryUrls {
        static public boolean isCoverUrl(String url) {
                return url != null && url.startsWith(COVER_URL_BASE);
        }
+
+       static public boolean isImprtUrl(String url) {
+               return url != null && url.startsWith(IMPRT_URL_BASE);
+       }
 }