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 java.util.LinkedList;
import java.util.List;
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 boolean rw;
- private boolean wl;
- private boolean bl;
-
public WLoginResult(boolean badLogin, boolean badCookie) {
super(badLogin, badCookie);
}
boolean wl, boolean bl) {
super(who, key, subkey, (rw ? "|rw" : "") + (wl ? "|wl" : "")
+ (bl ? "|bl" : "") + "|");
- this.rw = rw;
- this.wl = wl;
- this.bl = bl;
}
public WLoginResult(String cookie, String who, String key,
private List<String> whitelist;
private List<String> blacklist;
+ private Map<String, Progress> imprts = new HashMap<String, Progress>();
+
+ private boolean exiting;
+
public WebLibraryServer(boolean secure) throws IOException {
super(secure);
new Thread(this).start();
}
+ @Override
+ protected Response stop(WLoginResult login) {
+ if (!login.isRw()) {
+ return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_PLAINTEXT, "Exit not allowed");
+ }
+
+ if (exiting) {
+ return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE,
+ NanoHTTPD.MIME_PLAINTEXT, "Server is already exiting...");
+ }
+
+ exiting = true;
+ Instance.getInstance().getTraceHandler().trace("Exiting");
+
+ boolean ok;
+ do {
+ synchronized (imprts) {
+ ok = imprts.isEmpty();
+ }
+ if (!ok) {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ Instance.getInstance().getTraceHandler()
+ .trace("Waiting to exit...");
+ }
+ }
+ } while (!ok);
+
+ doStop();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1500);
+ } catch (InterruptedException e) {
+ }
+
+ Instance.getInstance().getTraceHandler()
+ .trace("Exit timeout: force-quit");
+ System.exit(0);
+ }
+ }, "Exit program after timeout of 1500 ms").start();
+
+ return NanoHTTPD.newFixedLengthResponse(Status.OK,
+ NanoHTTPD.MIME_PLAINTEXT, "Exited");
+ }
+
@Override
protected WLoginResult login(boolean badLogin, boolean badCookie) {
return new WLoginResult(false, false);
// /story/luid/json <-- json, whole chapter (no images)
@Override
protected Response getStoryPart(String uri, WLoginResult login) {
- String[] cover = uri.split("/");
+ String[] uriParts = uri.split("/");
int off = 2;
- if (cover.length < off + 2) {
+ if (uriParts.length < off + 2) {
return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
NanoHTTPD.MIME_PLAINTEXT, null);
}
- String luid = cover[off + 0];
- String chapterStr = cover[off + 1];
- String imageStr = cover.length < off + 3 ? null : cover[off + 2];
+ String luid = uriParts[off + 0];
+ String chapterStr = uriParts[off + 1];
+ String imageStr = uriParts.length < off + 3 ? null : uriParts[off + 2];
// 1-based (0 = desc)
int chapter = 0;
InputStream in = null;
try {
if ("cover".equals(chapterStr)) {
- Image img = cover(luid, login);
+ Image img = storyCover(luid, login);
if (img != null) {
in = img.newInputStream();
}
return newInputStreamResponse(mimeType, in);
}
+ // /story/luid/source
+ // /story/luid/title
+ // /story/luid/author
+ @Override
+ protected Response setStoryPart(String uri, String value,
+ WLoginResult login) throws IOException {
+ String[] uriParts = uri.split("/");
+ int off = 2; // "" and "story"
+
+ if (uriParts.length < off + 2) {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT, "Invalid story part request");
+ }
+
+ if (!login.isRw()) {
+ return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_PLAINTEXT, "SET story part not allowed");
+ }
+
+ if (exiting) {
+ return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE,
+ NanoHTTPD.MIME_PLAINTEXT, "Server is exiting...");
+ }
+
+ String luid = uriParts[off + 0];
+ String type = uriParts[off + 1];
+
+ if (!Arrays.asList("source", "title", "author").contains(type)) {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT,
+ "Invalid SET story part: " + type);
+ }
+
+ if (meta(luid, login) != null) {
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ if ("source".equals(type)) {
+ lib.changeSource(luid, value, null);
+ } else if ("title".equals(type)) {
+ lib.changeTitle(luid, value, null);
+ } else if ("author".equals(type)) {
+ lib.changeAuthor(luid, value, null);
+ }
+ }
+
+ return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null);
+ }
+
+ @Override
+ protected Response getCover(String uri, WLoginResult login)
+ throws IOException {
+ String[] uriParts = uri.split("/");
+ int off = 2; // "" and "cover"
+
+ if (uriParts.length < off + 2) {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request");
+ }
+
+ String type = uriParts[off + 0];
+ String id = uriParts[off + 1];
+
+ InputStream in = null;
+
+ if ("story".equals(type)) {
+ Image img = storyCover(id, login);
+ if (img != null) {
+ in = img.newInputStream();
+ }
+ } else if ("source".equals(type)) {
+ Image img = sourceCover(id, login);
+ if (img != null) {
+ in = img.newInputStream();
+ }
+ } else if ("author".equals(type)) {
+ Image img = authorCover(id, login);
+ if (img != null) {
+ in = img.newInputStream();
+ }
+ } else {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT,
+ "Invalid GET cover type: " + type);
+ }
+
+ // TODO: get correct image type
+ return newInputStreamResponse("image/png", in);
+ }
+
+ @Override
+ protected Response setCover(String uri, String luid, WLoginResult login)
+ throws IOException {
+ String[] uriParts = uri.split("/");
+ int off = 2; // "" and "cover"
+
+ if (uriParts.length < off + 2) {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request");
+ }
+
+ if (!login.isRw()) {
+ return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_PLAINTEXT, "Cover request not allowed");
+ }
+
+ if (exiting) {
+ return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE,
+ NanoHTTPD.MIME_PLAINTEXT, "Server is exiting...");
+ }
+
+ String type = uriParts[off + 0];
+ String id = uriParts[off + 1];
+
+ if ("source".equals(type)) {
+ sourceCover(id, login, luid);
+ } else if ("author".equals(type)) {
+ authorCover(id, login, luid);
+ } else {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT,
+ "Invalid SET cover type: " + type);
+ }
+
+ return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null);
+ }
+
+ @Override
+ protected Response imprt(String uri, String urlStr, WLoginResult login)
+ throws IOException {
+ final BasicLibrary lib = Instance.getInstance().getLibrary();
+
+ if (!login.isRw()) {
+ return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_PLAINTEXT, "Import not allowed");
+ }
+
+ if (exiting) {
+ return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE,
+ NanoHTTPD.MIME_PLAINTEXT, "Server is exiting...");
+ }
+
+ 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, luid, pg);
+ } catch (IOException e) {
+ Instance.getInstance().getTraceHandler().error(e);
+ } finally {
+ synchronized (imprts) {
+ imprts.remove(luid);
+ }
+ }
+ }
+ }, "Import story: " + urlStr).start();
+
+ 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 Response delete(String uri, WLoginResult login)
+ throws IOException {
+ String[] uriParts = uri.split("/");
+ int off = 2; // "" and "delete"
+
+ if (uriParts.length < off + 1) {
+ return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST,
+ NanoHTTPD.MIME_PLAINTEXT, "Invalid delete request");
+ }
+
+ if (!login.isRw()) {
+ return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_PLAINTEXT, "Delete not allowed");
+ }
+
+ if (exiting) {
+ return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE,
+ NanoHTTPD.MIME_PLAINTEXT, "Server is exiting...");
+ }
+
+ String luid = uriParts[off + 0];
+
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ lib.delete(luid);
+
+ return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null);
+ }
+
@Override
protected List<MetaData> metas(WLoginResult login) throws IOException {
BasicLibrary lib = Instance.getInstance().getLibrary();
return meta;
}
- private Image cover(String luid, WLoginResult login) throws IOException {
+ private Image storyCover(String luid, WLoginResult login)
+ throws IOException {
MetaData meta = meta(luid, login);
if (meta != null) {
BasicLibrary lib = Instance.getInstance().getLibrary();
return null;
}
+ private Image authorCover(String author, WLoginResult login)
+ throws IOException {
+ Image img = null;
+
+ List<MetaData> metas = new MetaResultList(metas(login)).filter(null,
+ author, null);
+ if (metas.size() > 0) {
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ img = lib.getCustomAuthorCover(author);
+ if (img == null)
+ img = lib.getCover(metas.get(0).getLuid());
+ }
+
+ return img;
+
+ }
+
+ private void authorCover(String author, WLoginResult login, String luid)
+ throws IOException {
+ if (meta(luid, login) != null) {
+ List<MetaData> metas = new MetaResultList(metas(login)).filter(null,
+ author, null);
+ if (metas.size() > 0) {
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ lib.setAuthorCover(author, luid);
+ }
+ }
+ }
+
+ private Image sourceCover(String source, WLoginResult login)
+ throws IOException {
+ Image img = null;
+
+ List<MetaData> metas = new MetaResultList(metas(login)).filter(source,
+ null, null);
+ if (metas.size() > 0) {
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ img = lib.getCustomSourceCover(source);
+ if (img == null)
+ img = lib.getCover(metas.get(0).getLuid());
+ }
+
+ return img;
+ }
+
+ private void sourceCover(String source, WLoginResult login, String luid)
+ throws IOException {
+ if (meta(luid, login) != null) {
+ List<MetaData> metas = new MetaResultList(metas(login))
+ .filter(source, null, null);
+ if (metas.size() > 0) {
+ BasicLibrary lib = Instance.getInstance().getLibrary();
+ lib.setSourceCover(source, luid);
+ }
+ }
+ }
+
private boolean isAllowed(MetaData meta, WLoginResult login) {
- if (login.isWl() && !whitelist.isEmpty()
- && !whitelist.contains(meta.getSource())) {
- return false;
+ MetaResultList one = new MetaResultList(Arrays.asList(meta));
+ if (login.isWl() && !whitelist.isEmpty()) {
+ if (one.filter(whitelist, null, null).isEmpty()) {
+ return false;
+ }
}
- if (login.isBl() && blacklist.contains(meta.getSource())) {
- return false;
+ if (login.isBl() && !blacklist.isEmpty()) {
+ if (!one.filter(blacklist, null, null).isEmpty()) {
+ return false;
+ }
}
return true;