From f70bcacf8bfccfff405d4fdaaa38b0e322945125 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Thu, 14 May 2020 12:05:30 +0200 Subject: [PATCH] weblib: full r/w support --- src/be/nikiroo/fanfix/data/JsonIO.java | 31 ++++++++ .../nikiroo/fanfix/library/BasicLibrary.java | 4 +- .../nikiroo/fanfix/library/CacheLibrary.java | 2 +- .../nikiroo/fanfix/library/LocalLibrary.java | 8 +-- .../nikiroo/fanfix/library/RemoteLibrary.java | 2 +- src/be/nikiroo/fanfix/library/WebLibrary.java | 70 ++++++++++++++++--- .../fanfix/library/WebLibraryServer.java | 62 ++++++++++++++++ .../fanfix/library/WebLibraryServerHtml.java | 12 ++++ .../fanfix/library/WebLibraryUrls.java | 24 ++++++- 9 files changed, 196 insertions(+), 19 deletions(-) diff --git a/src/be/nikiroo/fanfix/data/JsonIO.java b/src/be/nikiroo/fanfix/data/JsonIO.java index 5157dca..beff342 100644 --- a/src/be/nikiroo/fanfix/data/JsonIO.java +++ b/src/be/nikiroo/fanfix/data/JsonIO.java @@ -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 toListString(JSONArray array) { if (array != null) { List values = new ArrayList(); diff --git a/src/be/nikiroo/fanfix/library/BasicLibrary.java b/src/be/nikiroo/fanfix/library/BasicLibrary.java index af7920b..f6024ee 100644 --- a/src/be/nikiroo/fanfix/library/BasicLibrary.java +++ b/src/be/nikiroo/fanfix/library/BasicLibrary.java @@ -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); } diff --git a/src/be/nikiroo/fanfix/library/CacheLibrary.java b/src/be/nikiroo/fanfix/library/CacheLibrary.java index a3c3b5e..e184c1b 100644 --- a/src/be/nikiroo/fanfix/library/CacheLibrary.java +++ b/src/be/nikiroo/fanfix/library/CacheLibrary.java @@ -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"); } diff --git a/src/be/nikiroo/fanfix/library/LocalLibrary.java b/src/be/nikiroo/fanfix/library/LocalLibrary.java index 6720972..7220a39 100644 --- a/src/be/nikiroo/fanfix/library/LocalLibrary.java +++ b/src/be/nikiroo/fanfix/library/LocalLibrary.java @@ -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()); diff --git a/src/be/nikiroo/fanfix/library/RemoteLibrary.java b/src/be/nikiroo/fanfix/library/RemoteLibrary.java index 3a60e02..ad92800 100644 --- a/src/be/nikiroo/fanfix/library/RemoteLibrary.java +++ b/src/be/nikiroo/fanfix/library/RemoteLibrary.java @@ -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"); } diff --git a/src/be/nikiroo/fanfix/library/WebLibrary.java b/src/be/nikiroo/fanfix/library/WebLibrary.java index 040acd0..9d1c773 100644 --- a/src/be/nikiroo/fanfix/library/WebLibrary.java +++ b/src/be/nikiroo/fanfix/library/WebLibrary.java @@ -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. + *

+ * 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 post = new HashMap(); + 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"); } diff --git a/src/be/nikiroo/fanfix/library/WebLibraryServer.java b/src/be/nikiroo/fanfix/library/WebLibraryServer.java index 55e8ed3..5cda3b5 100644 --- a/src/be/nikiroo/fanfix/library/WebLibraryServer.java +++ b/src/be/nikiroo/fanfix/library/WebLibraryServer.java @@ -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 whitelist; private List blacklist; + private Map imprts = new HashMap(); + 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 metas(WLoginResult login) throws IOException { BasicLibrary lib = Instance.getInstance().getLibrary(); diff --git a/src/be/nikiroo/fanfix/library/WebLibraryServerHtml.java b/src/be/nikiroo/fanfix/library/WebLibraryServerHtml.java index 77b8efe..a534b4c 100644 --- a/src/be/nikiroo/fanfix/library/WebLibraryServerHtml.java +++ b/src/be/nikiroo/fanfix/library/WebLibraryServerHtml.java @@ -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: " diff --git a/src/be/nikiroo/fanfix/library/WebLibraryUrls.java b/src/be/nikiroo/fanfix/library/WebLibraryUrls.java index c1d1cf2..41f1c82 100644 --- a/src/be/nikiroo/fanfix/library/WebLibraryUrls.java +++ b/src/be/nikiroo/fanfix/library/WebLibraryUrls.java @@ -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); + } } -- 2.27.0