Commit | Line | Data |
---|---|---|
f433d153 NR |
1 | package be.nikiroo.fanfix.library; |
2 | ||
3 | import java.io.ByteArrayInputStream; | |
f433d153 NR |
4 | import java.io.IOException; |
5 | import java.io.InputStream; | |
f70bcacf | 6 | import java.net.URL; |
f433d153 | 7 | import java.util.ArrayList; |
5c4ce687 | 8 | import java.util.Arrays; |
f433d153 NR |
9 | import java.util.HashMap; |
10 | import java.util.LinkedList; | |
11 | import java.util.List; | |
12 | import java.util.Map; | |
13 | ||
f433d153 NR |
14 | import org.json.JSONArray; |
15 | import org.json.JSONObject; | |
16 | ||
17 | import be.nikiroo.fanfix.Instance; | |
18 | import be.nikiroo.fanfix.bundles.Config; | |
f433d153 NR |
19 | import be.nikiroo.fanfix.data.Chapter; |
20 | import be.nikiroo.fanfix.data.JsonIO; | |
21 | import be.nikiroo.fanfix.data.MetaData; | |
22 | import be.nikiroo.fanfix.data.Paragraph; | |
23 | import be.nikiroo.fanfix.data.Paragraph.ParagraphType; | |
24 | import be.nikiroo.fanfix.data.Story; | |
f433d153 | 25 | import be.nikiroo.utils.Image; |
fce0a73f | 26 | import be.nikiroo.utils.LoginResult; |
f433d153 | 27 | import be.nikiroo.utils.NanoHTTPD; |
f433d153 NR |
28 | import be.nikiroo.utils.NanoHTTPD.Response; |
29 | import be.nikiroo.utils.NanoHTTPD.Response.Status; | |
f70bcacf | 30 | import be.nikiroo.utils.Progress; |
f433d153 | 31 | |
33d40b9f NR |
32 | public class WebLibraryServer extends WebLibraryServerHtml { |
33 | class WLoginResult extends LoginResult { | |
fce0a73f NR |
34 | public WLoginResult(boolean badLogin, boolean badCookie) { |
35 | super(badLogin, badCookie); | |
36 | } | |
37 | ||
38 | public WLoginResult(String who, String key, String subkey, boolean rw, | |
39 | boolean wl, boolean bl) { | |
40 | super(who, key, subkey, (rw ? "|rw" : "") + (wl ? "|wl" : "") | |
41 | + (bl ? "|bl" : "") + "|"); | |
f433d153 NR |
42 | } |
43 | ||
fce0a73f | 44 | public WLoginResult(String cookie, String who, String key, |
f433d153 | 45 | List<String> subkeys) { |
fce0a73f NR |
46 | super(cookie, who, key, subkeys, |
47 | subkeys == null || subkeys.isEmpty()); | |
f433d153 NR |
48 | } |
49 | ||
50 | public boolean isRw() { | |
fce0a73f | 51 | return getOption().contains("|rw|"); |
f433d153 NR |
52 | } |
53 | ||
54 | public boolean isWl() { | |
fce0a73f | 55 | return getOption().contains("|wl|"); |
f433d153 NR |
56 | } |
57 | ||
d11fb35b | 58 | public boolean isBl() { |
fce0a73f | 59 | return getOption().contains("|bl|"); |
f433d153 NR |
60 | } |
61 | } | |
62 | ||
f433d153 NR |
63 | private Map<String, Story> storyCache = new HashMap<String, Story>(); |
64 | private LinkedList<String> storyCacheOrder = new LinkedList<String>(); | |
65 | private long storyCacheSize = 0; | |
66 | private long maxStoryCacheSize; | |
f433d153 | 67 | |
d11fb35b NR |
68 | private List<String> whitelist; |
69 | private List<String> blacklist; | |
70 | ||
f70bcacf NR |
71 | private Map<String, Progress> imprts = new HashMap<String, Progress>(); |
72 | ||
09c2396e NR |
73 | private boolean exiting; |
74 | ||
f433d153 | 75 | public WebLibraryServer(boolean secure) throws IOException { |
33d40b9f | 76 | super(secure); |
f433d153 NR |
77 | |
78 | int cacheMb = Instance.getInstance().getConfig() | |
79 | .getInteger(Config.SERVER_MAX_CACHE_MB, 100); | |
80 | maxStoryCacheSize = cacheMb * 1024 * 1024; | |
81 | ||
82 | setTraceHandler(Instance.getInstance().getTraceHandler()); | |
83 | ||
d11fb35b NR |
84 | whitelist = Instance.getInstance().getConfig() |
85 | .getList(Config.SERVER_WHITELIST, new ArrayList<String>()); | |
86 | blacklist = Instance.getInstance().getConfig() | |
87 | .getList(Config.SERVER_BLACKLIST, new ArrayList<String>()); | |
f433d153 NR |
88 | } |
89 | ||
90 | /** | |
91 | * Start the server (listen on the network for new connections). | |
92 | * <p> | |
93 | * Can only be called once. | |
94 | * <p> | |
95 | * This call is asynchronous, and will just start a new {@link Thread} on | |
96 | * itself (see {@link WebLibraryServer#run()}). | |
97 | */ | |
98 | public void start() { | |
99 | new Thread(this).start(); | |
100 | } | |
101 | ||
09c2396e NR |
102 | @Override |
103 | protected Response stop(WLoginResult login) { | |
104 | if (!login.isRw()) { | |
105 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
106 | NanoHTTPD.MIME_PLAINTEXT, "Exit not allowed"); | |
107 | } | |
108 | ||
109 | if (exiting) { | |
110 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
111 | NanoHTTPD.MIME_PLAINTEXT, "Server is already exiting..."); | |
112 | } | |
cab46763 | 113 | |
09c2396e NR |
114 | exiting = true; |
115 | Instance.getInstance().getTraceHandler().trace("Exiting"); | |
116 | ||
117 | boolean ok; | |
118 | do { | |
119 | synchronized (imprts) { | |
120 | ok = imprts.isEmpty(); | |
121 | } | |
122 | if (!ok) { | |
123 | try { | |
124 | Thread.sleep(2000); | |
125 | } catch (InterruptedException e) { | |
126 | Instance.getInstance().getTraceHandler() | |
127 | .trace("Waiting to exit..."); | |
128 | } | |
129 | } | |
130 | } while (!ok); | |
131 | ||
132 | doStop(); | |
133 | ||
cab46763 NR |
134 | new Thread(new Runnable() { |
135 | @Override | |
136 | public void run() { | |
137 | try { | |
138 | Thread.sleep(1500); | |
139 | } catch (InterruptedException e) { | |
140 | } | |
141 | ||
142 | Instance.getInstance().getTraceHandler() | |
143 | .trace("Exit timeout: force-quit"); | |
144 | System.exit(0); | |
145 | } | |
146 | }, "Exit program after timeout of 1500 ms").start(); | |
147 | ||
09c2396e NR |
148 | return NanoHTTPD.newFixedLengthResponse(Status.OK, |
149 | NanoHTTPD.MIME_PLAINTEXT, "Exited"); | |
150 | } | |
151 | ||
33d40b9f NR |
152 | @Override |
153 | protected WLoginResult login(boolean badLogin, boolean badCookie) { | |
154 | return new WLoginResult(false, false); | |
f433d153 NR |
155 | } |
156 | ||
33d40b9f NR |
157 | @Override |
158 | protected WLoginResult login(String who, String cookie) { | |
fce0a73f NR |
159 | List<String> subkeys = Instance.getInstance().getConfig() |
160 | .getList(Config.SERVER_ALLOWED_SUBKEYS); | |
f433d153 | 161 | String realKey = Instance.getInstance().getConfig() |
fce0a73f | 162 | .getString(Config.SERVER_KEY); |
d11fb35b | 163 | |
fce0a73f | 164 | return new WLoginResult(cookie, who, realKey, subkeys); |
f433d153 NR |
165 | } |
166 | ||
167 | // allow rw/wl | |
33d40b9f NR |
168 | @Override |
169 | protected WLoginResult login(String who, String key, String subkey) { | |
f433d153 | 170 | String realKey = Instance.getInstance().getConfig() |
d11fb35b | 171 | .getString(Config.SERVER_KEY, ""); |
f433d153 NR |
172 | |
173 | // I don't like NULLs... | |
f433d153 NR |
174 | key = key == null ? "" : key; |
175 | subkey = subkey == null ? "" : subkey; | |
176 | ||
177 | if (!realKey.equals(key)) { | |
fce0a73f | 178 | return new WLoginResult(true, false); |
f433d153 NR |
179 | } |
180 | ||
d11fb35b | 181 | // defaults are true (as previous versions without the feature) |
f433d153 NR |
182 | boolean rw = true; |
183 | boolean wl = true; | |
d11fb35b | 184 | boolean bl = true; |
f433d153 | 185 | |
599b05c7 NR |
186 | rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW, |
187 | rw); | |
fce0a73f NR |
188 | |
189 | List<String> allowed = Instance.getInstance().getConfig().getList( | |
190 | Config.SERVER_ALLOWED_SUBKEYS, new ArrayList<String>()); | |
191 | ||
192 | if (!allowed.isEmpty()) { | |
193 | if (!allowed.contains(subkey)) { | |
194 | return new WLoginResult(true, false); | |
195 | } | |
196 | ||
197 | if ((subkey + "|").contains("|rw|")) { | |
198 | rw = true; | |
199 | } | |
200 | if ((subkey + "|").contains("|wl|")) { | |
201 | wl = false; // |wl| = bypass whitelist | |
202 | } | |
203 | if ((subkey + "|").contains("|bl|")) { | |
204 | bl = false; // |bl| = bypass blacklist | |
f433d153 NR |
205 | } |
206 | } | |
207 | ||
fce0a73f | 208 | return new WLoginResult(who, key, subkey, rw, wl, bl); |
f433d153 NR |
209 | } |
210 | ||
33d40b9f | 211 | @Override |
fce0a73f | 212 | protected Response getList(String uri, WLoginResult login) |
f433d153 | 213 | throws IOException { |
5ee0fc14 | 214 | if (WebLibraryUrls.LIST_URL_METADATA.equals(uri)) { |
f433d153 | 215 | List<JSONObject> jsons = new ArrayList<JSONObject>(); |
d11fb35b | 216 | for (MetaData meta : metas(login)) { |
f433d153 NR |
217 | jsons.add(JsonIO.toJson(meta)); |
218 | } | |
219 | ||
bd743ca2 NR |
220 | return NanoHTTPD.newFixedLengthResponse(Status.OK, |
221 | "application/json", new JSONArray(jsons).toString()); | |
f433d153 NR |
222 | } |
223 | ||
224 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
225 | NanoHTTPD.MIME_PLAINTEXT, null); | |
226 | } | |
227 | ||
f433d153 NR |
228 | // /story/luid/chapter/para <-- text/image |
229 | // /story/luid/cover <-- image | |
230 | // /story/luid/metadata <-- json | |
c5103223 | 231 | // /story/luid/json <-- json, whole chapter (no images) |
33d40b9f NR |
232 | @Override |
233 | protected Response getStoryPart(String uri, WLoginResult login) { | |
6673ec59 | 234 | String[] uriParts = uri.split("/"); |
f433d153 NR |
235 | int off = 2; |
236 | ||
6673ec59 | 237 | if (uriParts.length < off + 2) { |
f433d153 NR |
238 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, |
239 | NanoHTTPD.MIME_PLAINTEXT, null); | |
240 | } | |
241 | ||
6673ec59 NR |
242 | String luid = uriParts[off + 0]; |
243 | String chapterStr = uriParts[off + 1]; | |
244 | String imageStr = uriParts.length < off + 3 ? null : uriParts[off + 2]; | |
f433d153 NR |
245 | |
246 | // 1-based (0 = desc) | |
247 | int chapter = 0; | |
248 | if (chapterStr != null && !"cover".equals(chapterStr) | |
599b05c7 NR |
249 | && !"metadata".equals(chapterStr) |
250 | && !"json".equals(chapterStr)) { | |
f433d153 NR |
251 | try { |
252 | chapter = Integer.parseInt(chapterStr); | |
253 | if (chapter < 0) { | |
254 | throw new NumberFormatException(); | |
255 | } | |
256 | } catch (NumberFormatException e) { | |
257 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
258 | NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid"); | |
259 | } | |
260 | } | |
261 | ||
262 | // 1-based | |
263 | int paragraph = 1; | |
264 | if (imageStr != null) { | |
265 | try { | |
266 | paragraph = Integer.parseInt(imageStr); | |
267 | if (paragraph < 0) { | |
268 | throw new NumberFormatException(); | |
269 | } | |
270 | } catch (NumberFormatException e) { | |
271 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
272 | NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid"); | |
273 | } | |
274 | } | |
275 | ||
276 | String mimeType = NanoHTTPD.MIME_PLAINTEXT; | |
277 | InputStream in = null; | |
278 | try { | |
279 | if ("cover".equals(chapterStr)) { | |
6673ec59 | 280 | Image img = storyCover(luid, login); |
f433d153 NR |
281 | if (img != null) { |
282 | in = img.newInputStream(); | |
283 | } | |
3f468ac7 NR |
284 | // TODO: get correct image type |
285 | mimeType = "image/png"; | |
f433d153 | 286 | } else if ("metadata".equals(chapterStr)) { |
d11fb35b | 287 | MetaData meta = meta(luid, login); |
f433d153 NR |
288 | JSONObject json = JsonIO.toJson(meta); |
289 | mimeType = "application/json"; | |
290 | in = new ByteArrayInputStream(json.toString().getBytes()); | |
3fbc084c | 291 | } else if ("json".equals(chapterStr)) { |
d11fb35b | 292 | Story story = story(luid, login); |
c5103223 NR |
293 | JSONObject json = JsonIO.toJson(story); |
294 | mimeType = "application/json"; | |
295 | in = new ByteArrayInputStream(json.toString().getBytes()); | |
f433d153 | 296 | } else { |
d11fb35b | 297 | Story story = story(luid, login); |
f433d153 NR |
298 | if (story != null) { |
299 | if (chapter == 0) { | |
300 | StringBuilder builder = new StringBuilder(); | |
301 | for (Paragraph p : story.getMeta().getResume()) { | |
302 | if (builder.length() == 0) { | |
303 | builder.append("\n"); | |
304 | } | |
305 | builder.append(p.getContent()); | |
306 | } | |
307 | ||
599b05c7 NR |
308 | in = new ByteArrayInputStream( |
309 | builder.toString().getBytes("utf-8")); | |
f433d153 NR |
310 | } else { |
311 | Paragraph para = story.getChapters().get(chapter - 1) | |
312 | .getParagraphs().get(paragraph - 1); | |
313 | Image img = para.getContentImage(); | |
314 | if (para.getType() == ParagraphType.IMAGE) { | |
315 | // TODO: get correct image type | |
316 | mimeType = "image/png"; | |
317 | in = img.newInputStream(); | |
318 | } else { | |
599b05c7 NR |
319 | in = new ByteArrayInputStream( |
320 | para.getContent().getBytes("utf-8")); | |
f433d153 NR |
321 | } |
322 | } | |
323 | } | |
324 | } | |
325 | } catch (IndexOutOfBoundsException e) { | |
326 | return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, | |
327 | NanoHTTPD.MIME_PLAINTEXT, | |
328 | "Chapter or paragraph does not exist"); | |
329 | } catch (IOException e) { | |
330 | Instance.getInstance().getTraceHandler() | |
331 | .error(new IOException("Cannot get image: " + uri, e)); | |
332 | return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, | |
333 | NanoHTTPD.MIME_PLAINTEXT, "Error when processing request"); | |
334 | } | |
335 | ||
336 | return newInputStreamResponse(mimeType, in); | |
337 | } | |
338 | ||
089e354e NR |
339 | // /story/luid/source |
340 | // /story/luid/title | |
341 | // /story/luid/author | |
342 | @Override | |
343 | protected Response setStoryPart(String uri, String value, | |
344 | WLoginResult login) throws IOException { | |
345 | String[] uriParts = uri.split("/"); | |
346 | int off = 2; // "" and "story" | |
347 | ||
348 | if (uriParts.length < off + 2) { | |
349 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
350 | NanoHTTPD.MIME_PLAINTEXT, "Invalid story part request"); | |
351 | } | |
352 | ||
a1226ce0 NR |
353 | if (!login.isRw()) { |
354 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
355 | NanoHTTPD.MIME_PLAINTEXT, "SET story part not allowed"); | |
356 | } | |
cab46763 | 357 | |
09c2396e NR |
358 | if (exiting) { |
359 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
360 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
361 | } | |
a1226ce0 | 362 | |
089e354e NR |
363 | String luid = uriParts[off + 0]; |
364 | String type = uriParts[off + 1]; | |
365 | ||
366 | if (!Arrays.asList("source", "title", "author").contains(type)) { | |
367 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
368 | NanoHTTPD.MIME_PLAINTEXT, | |
369 | "Invalid SET story part: " + type); | |
370 | } | |
371 | ||
372 | if (meta(luid, login) != null) { | |
373 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
374 | if ("source".equals(type)) { | |
375 | lib.changeSource(luid, value, null); | |
376 | } else if ("title".equals(type)) { | |
377 | lib.changeTitle(luid, value, null); | |
378 | } else if ("author".equals(type)) { | |
379 | lib.changeAuthor(luid, value, null); | |
380 | } | |
381 | } | |
382 | ||
383 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
384 | } | |
385 | ||
6673ec59 NR |
386 | @Override |
387 | protected Response getCover(String uri, WLoginResult login) | |
388 | throws IOException { | |
389 | String[] uriParts = uri.split("/"); | |
9351b696 | 390 | int startAt = 0; |
6673ec59 | 391 | int off = 2; // "" and "cover" |
9351b696 NR |
392 | for (int i = 0; i < off; i++) { |
393 | startAt += uriParts[i].length() + "/".length(); | |
394 | } | |
6673ec59 NR |
395 | |
396 | if (uriParts.length < off + 2) { | |
397 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
398 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
399 | } | |
400 | ||
401 | String type = uriParts[off + 0]; | |
9351b696 | 402 | String id = uri.substring(startAt + type.length() + "/".length()); |
6673ec59 NR |
403 | |
404 | InputStream in = null; | |
405 | ||
e4b1b70c | 406 | if ("story".equals(type)) { |
6673ec59 NR |
407 | Image img = storyCover(id, login); |
408 | if (img != null) { | |
409 | in = img.newInputStream(); | |
410 | } | |
e4b1b70c NR |
411 | } else if ("source".equals(type)) { |
412 | Image img = sourceCover(id, login); | |
6673ec59 NR |
413 | if (img != null) { |
414 | in = img.newInputStream(); | |
415 | } | |
e4b1b70c NR |
416 | } else if ("author".equals(type)) { |
417 | Image img = authorCover(id, login); | |
6673ec59 NR |
418 | if (img != null) { |
419 | in = img.newInputStream(); | |
420 | } | |
421 | } else { | |
422 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
e4b1b70c NR |
423 | NanoHTTPD.MIME_PLAINTEXT, |
424 | "Invalid GET cover type: " + type); | |
6673ec59 NR |
425 | } |
426 | ||
427 | // TODO: get correct image type | |
428 | return newInputStreamResponse("image/png", in); | |
429 | } | |
430 | ||
e4b1b70c NR |
431 | @Override |
432 | protected Response setCover(String uri, String luid, WLoginResult login) | |
433 | throws IOException { | |
434 | String[] uriParts = uri.split("/"); | |
435 | int off = 2; // "" and "cover" | |
9351b696 NR |
436 | int startAt = 0; |
437 | for (int i = 0; i < off; i++) { | |
438 | startAt += uriParts[i].length() + "/".length(); | |
439 | } | |
e4b1b70c NR |
440 | |
441 | if (uriParts.length < off + 2) { | |
442 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
443 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
444 | } | |
445 | ||
a1226ce0 NR |
446 | if (!login.isRw()) { |
447 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
448 | NanoHTTPD.MIME_PLAINTEXT, "Cover request not allowed"); | |
449 | } | |
cab46763 | 450 | |
09c2396e NR |
451 | if (exiting) { |
452 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
453 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
454 | } | |
a1226ce0 | 455 | |
e4b1b70c | 456 | String type = uriParts[off + 0]; |
9351b696 | 457 | String id = uri.substring(startAt + type.length() + "/".length()); |
e4b1b70c NR |
458 | |
459 | if ("source".equals(type)) { | |
460 | sourceCover(id, login, luid); | |
461 | } else if ("author".equals(type)) { | |
462 | authorCover(id, login, luid); | |
463 | } else { | |
464 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
465 | NanoHTTPD.MIME_PLAINTEXT, | |
466 | "Invalid SET cover type: " + type); | |
467 | } | |
468 | ||
469 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
470 | } | |
471 | ||
f70bcacf NR |
472 | @Override |
473 | protected Response imprt(String uri, String urlStr, WLoginResult login) | |
474 | throws IOException { | |
475 | final BasicLibrary lib = Instance.getInstance().getLibrary(); | |
476 | ||
a1226ce0 NR |
477 | if (!login.isRw()) { |
478 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
479 | NanoHTTPD.MIME_PLAINTEXT, "Import not allowed"); | |
480 | } | |
cab46763 | 481 | |
09c2396e NR |
482 | if (exiting) { |
483 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
484 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
485 | } | |
a1226ce0 | 486 | |
f70bcacf NR |
487 | final URL url = new URL(urlStr); |
488 | final Progress pg = new Progress(); | |
489 | final String luid = lib.getNextId(); | |
490 | ||
491 | synchronized (imprts) { | |
492 | imprts.put(luid, pg); | |
493 | } | |
494 | ||
495 | new Thread(new Runnable() { | |
496 | @Override | |
497 | public void run() { | |
498 | try { | |
acbec0d2 | 499 | lib.imprt(url, luid, pg); |
f70bcacf NR |
500 | } catch (IOException e) { |
501 | Instance.getInstance().getTraceHandler().error(e); | |
502 | } finally { | |
503 | synchronized (imprts) { | |
504 | imprts.remove(luid); | |
505 | } | |
506 | } | |
507 | } | |
508 | }, "Import story: " + urlStr).start(); | |
509 | ||
f70bcacf NR |
510 | return NanoHTTPD.newFixedLengthResponse(Status.OK, |
511 | NanoHTTPD.MIME_PLAINTEXT, luid); | |
512 | } | |
513 | ||
514 | @Override | |
515 | protected Response imprtProgress(String uri, WLoginResult login) { | |
516 | String[] uriParts = uri.split("/"); | |
517 | int off = 2; // "" and "import" | |
518 | ||
519 | if (uriParts.length < off + 1) { | |
520 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
521 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
522 | } | |
523 | ||
524 | String luid = uriParts[off + 0]; | |
525 | ||
526 | Progress pg = null; | |
527 | synchronized (imprts) { | |
528 | pg = imprts.get(luid); | |
529 | } | |
530 | if (pg != null) { | |
531 | return NanoHTTPD.newFixedLengthResponse(Status.OK, | |
532 | "application/json", JsonIO.toJson(pg).toString()); | |
533 | } | |
534 | ||
535 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
536 | } | |
537 | ||
a1226ce0 NR |
538 | @Override |
539 | protected Response delete(String uri, WLoginResult login) | |
540 | throws IOException { | |
541 | String[] uriParts = uri.split("/"); | |
542 | int off = 2; // "" and "delete" | |
543 | ||
544 | if (uriParts.length < off + 1) { | |
545 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
546 | NanoHTTPD.MIME_PLAINTEXT, "Invalid delete request"); | |
547 | } | |
548 | ||
549 | if (!login.isRw()) { | |
550 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
551 | NanoHTTPD.MIME_PLAINTEXT, "Delete not allowed"); | |
552 | } | |
cab46763 | 553 | |
09c2396e NR |
554 | if (exiting) { |
555 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
556 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
557 | } | |
a1226ce0 NR |
558 | |
559 | String luid = uriParts[off + 0]; | |
560 | ||
561 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
562 | lib.delete(luid); | |
563 | ||
564 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
565 | } | |
566 | ||
33d40b9f NR |
567 | @Override |
568 | protected List<MetaData> metas(WLoginResult login) throws IOException { | |
d11fb35b NR |
569 | BasicLibrary lib = Instance.getInstance().getLibrary(); |
570 | List<MetaData> metas = new ArrayList<MetaData>(); | |
571 | for (MetaData meta : lib.getList().getMetas()) { | |
572 | if (isAllowed(meta, login)) { | |
573 | metas.add(meta); | |
574 | } | |
575 | } | |
576 | ||
577 | return metas; | |
578 | } | |
579 | ||
f433d153 | 580 | // NULL if not whitelist OK or if not found |
33d40b9f NR |
581 | @Override |
582 | protected Story story(String luid, WLoginResult login) throws IOException { | |
f433d153 NR |
583 | synchronized (storyCache) { |
584 | if (storyCache.containsKey(luid)) { | |
585 | Story story = storyCache.get(luid); | |
d11fb35b | 586 | if (!isAllowed(story.getMeta(), login)) |
f433d153 | 587 | return null; |
f433d153 NR |
588 | |
589 | return story; | |
590 | } | |
591 | } | |
592 | ||
593 | Story story = null; | |
d11fb35b | 594 | MetaData meta = meta(luid, login); |
f433d153 NR |
595 | if (meta != null) { |
596 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
597 | story = lib.getStory(luid, null); | |
598 | long size = sizeOf(story); | |
599 | ||
600 | synchronized (storyCache) { | |
601 | // Could have been added by another request | |
602 | if (!storyCache.containsKey(luid)) { | |
603 | while (!storyCacheOrder.isEmpty() | |
604 | && storyCacheSize + size > maxStoryCacheSize) { | |
605 | String oldestLuid = storyCacheOrder.removeFirst(); | |
606 | Story oldestStory = storyCache.remove(oldestLuid); | |
607 | maxStoryCacheSize -= sizeOf(oldestStory); | |
608 | } | |
609 | ||
610 | storyCacheOrder.add(luid); | |
611 | storyCache.put(luid, story); | |
612 | } | |
613 | } | |
614 | } | |
615 | ||
616 | return story; | |
617 | } | |
618 | ||
33d40b9f NR |
619 | private MetaData meta(String luid, WLoginResult login) throws IOException { |
620 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
621 | MetaData meta = lib.getInfo(luid); | |
622 | if (!isAllowed(meta, login)) | |
623 | return null; | |
f433d153 | 624 | |
33d40b9f | 625 | return meta; |
f433d153 NR |
626 | } |
627 | ||
6673ec59 NR |
628 | private Image storyCover(String luid, WLoginResult login) |
629 | throws IOException { | |
33d40b9f NR |
630 | MetaData meta = meta(luid, login); |
631 | if (meta != null) { | |
632 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
633 | return lib.getCover(meta.getLuid()); | |
f433d153 | 634 | } |
f433d153 | 635 | |
33d40b9f | 636 | return null; |
f433d153 NR |
637 | } |
638 | ||
6673ec59 NR |
639 | private Image authorCover(String author, WLoginResult login) |
640 | throws IOException { | |
641 | Image img = null; | |
642 | ||
643 | List<MetaData> metas = new MetaResultList(metas(login)).filter(null, | |
644 | author, null); | |
645 | if (metas.size() > 0) { | |
646 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
647 | img = lib.getCustomAuthorCover(author); | |
648 | if (img == null) | |
649 | img = lib.getCover(metas.get(0).getLuid()); | |
650 | } | |
651 | ||
652 | return img; | |
653 | ||
654 | } | |
655 | ||
e4b1b70c NR |
656 | private void authorCover(String author, WLoginResult login, String luid) |
657 | throws IOException { | |
658 | if (meta(luid, login) != null) { | |
659 | List<MetaData> metas = new MetaResultList(metas(login)).filter(null, | |
660 | author, null); | |
661 | if (metas.size() > 0) { | |
662 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
663 | lib.setAuthorCover(author, luid); | |
664 | } | |
665 | } | |
666 | } | |
667 | ||
6673ec59 NR |
668 | private Image sourceCover(String source, WLoginResult login) |
669 | throws IOException { | |
670 | Image img = null; | |
671 | ||
672 | List<MetaData> metas = new MetaResultList(metas(login)).filter(source, | |
673 | null, null); | |
674 | if (metas.size() > 0) { | |
675 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
676 | img = lib.getCustomSourceCover(source); | |
677 | if (img == null) | |
678 | img = lib.getCover(metas.get(0).getLuid()); | |
679 | } | |
680 | ||
681 | return img; | |
682 | } | |
683 | ||
e4b1b70c NR |
684 | private void sourceCover(String source, WLoginResult login, String luid) |
685 | throws IOException { | |
686 | if (meta(luid, login) != null) { | |
687 | List<MetaData> metas = new MetaResultList(metas(login)) | |
688 | .filter(source, null, null); | |
689 | if (metas.size() > 0) { | |
690 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
691 | lib.setSourceCover(source, luid); | |
692 | } | |
693 | } | |
694 | } | |
695 | ||
33d40b9f | 696 | private boolean isAllowed(MetaData meta, WLoginResult login) { |
5c4ce687 NR |
697 | MetaResultList one = new MetaResultList(Arrays.asList(meta)); |
698 | if (login.isWl() && !whitelist.isEmpty()) { | |
699 | if (one.filter(whitelist, null, null).isEmpty()) { | |
700 | return false; | |
701 | } | |
f433d153 | 702 | } |
5c4ce687 NR |
703 | if (login.isBl() && !blacklist.isEmpty()) { |
704 | if (!one.filter(blacklist, null, null).isEmpty()) { | |
705 | return false; | |
706 | } | |
599b05c7 NR |
707 | } |
708 | ||
33d40b9f | 709 | return true; |
599b05c7 NR |
710 | } |
711 | ||
33d40b9f NR |
712 | private long sizeOf(Story story) { |
713 | long size = 0; | |
714 | for (Chapter chap : story) { | |
715 | for (Paragraph para : chap) { | |
716 | if (para.getType() == ParagraphType.IMAGE) { | |
717 | size += para.getContentImage().getSize(); | |
718 | } else { | |
719 | size += para.getContent().length(); | |
720 | } | |
721 | } | |
6b89e45c NR |
722 | } |
723 | ||
33d40b9f | 724 | return size; |
6b89e45c NR |
725 | } |
726 | ||
3fbc084c NR |
727 | public static void main(String[] args) throws IOException { |
728 | Instance.init(); | |
729 | WebLibraryServer web = new WebLibraryServer(false); | |
730 | web.run(); | |
731 | } | |
f433d153 | 732 | } |