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) {
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>();
*
* @return the next luid
*/
- protected abstract int getNextId();
+ protected abstract String getNextId();
/**
* Delete the target {@link Story}.
pg.setName("Saving story");
if (luid == null || luid.isEmpty()) {
- meta.setLuid(String.format("%03d", getNextId()));
+ meta.setLuid(getNextId());
} else {
meta.setLuid(luid);
}
// BasicLibrary:
@Override
- protected int getNextId() {
+ protected String getNextId() {
throw new java.lang.InternalError("Should not have been called");
}
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.
}
@Override
- protected int getNextId() {
+ protected String getNextId() {
getStories(null); // make sure lastId is set
synchronized (lock) {
- return ++lastId;
+ return String.format("%03d", ++lastId);
}
}
*/
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());
// 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");
}
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);
@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));
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) {
}
}
+ pg.add(1);
number++;
}
chapNum++;
}
+ pg.done();
return story;
}
@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
// 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
// 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");
}
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;
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 {
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);
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();
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);
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: "
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
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/";
.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) {
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);
+ }
}