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 | } | |
113 | ||
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 | ||
134 | return NanoHTTPD.newFixedLengthResponse(Status.OK, | |
135 | NanoHTTPD.MIME_PLAINTEXT, "Exited"); | |
136 | } | |
137 | ||
33d40b9f NR |
138 | @Override |
139 | protected WLoginResult login(boolean badLogin, boolean badCookie) { | |
140 | return new WLoginResult(false, false); | |
f433d153 NR |
141 | } |
142 | ||
33d40b9f NR |
143 | @Override |
144 | protected WLoginResult login(String who, String cookie) { | |
fce0a73f NR |
145 | List<String> subkeys = Instance.getInstance().getConfig() |
146 | .getList(Config.SERVER_ALLOWED_SUBKEYS); | |
f433d153 | 147 | String realKey = Instance.getInstance().getConfig() |
fce0a73f | 148 | .getString(Config.SERVER_KEY); |
d11fb35b | 149 | |
fce0a73f | 150 | return new WLoginResult(cookie, who, realKey, subkeys); |
f433d153 NR |
151 | } |
152 | ||
153 | // allow rw/wl | |
33d40b9f NR |
154 | @Override |
155 | protected WLoginResult login(String who, String key, String subkey) { | |
f433d153 | 156 | String realKey = Instance.getInstance().getConfig() |
d11fb35b | 157 | .getString(Config.SERVER_KEY, ""); |
f433d153 NR |
158 | |
159 | // I don't like NULLs... | |
f433d153 NR |
160 | key = key == null ? "" : key; |
161 | subkey = subkey == null ? "" : subkey; | |
162 | ||
163 | if (!realKey.equals(key)) { | |
fce0a73f | 164 | return new WLoginResult(true, false); |
f433d153 NR |
165 | } |
166 | ||
d11fb35b | 167 | // defaults are true (as previous versions without the feature) |
f433d153 NR |
168 | boolean rw = true; |
169 | boolean wl = true; | |
d11fb35b | 170 | boolean bl = true; |
f433d153 | 171 | |
599b05c7 NR |
172 | rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW, |
173 | rw); | |
fce0a73f NR |
174 | |
175 | List<String> allowed = Instance.getInstance().getConfig().getList( | |
176 | Config.SERVER_ALLOWED_SUBKEYS, new ArrayList<String>()); | |
177 | ||
178 | if (!allowed.isEmpty()) { | |
179 | if (!allowed.contains(subkey)) { | |
180 | return new WLoginResult(true, false); | |
181 | } | |
182 | ||
183 | if ((subkey + "|").contains("|rw|")) { | |
184 | rw = true; | |
185 | } | |
186 | if ((subkey + "|").contains("|wl|")) { | |
187 | wl = false; // |wl| = bypass whitelist | |
188 | } | |
189 | if ((subkey + "|").contains("|bl|")) { | |
190 | bl = false; // |bl| = bypass blacklist | |
f433d153 NR |
191 | } |
192 | } | |
193 | ||
fce0a73f | 194 | return new WLoginResult(who, key, subkey, rw, wl, bl); |
f433d153 NR |
195 | } |
196 | ||
33d40b9f | 197 | @Override |
fce0a73f | 198 | protected Response getList(String uri, WLoginResult login) |
f433d153 | 199 | throws IOException { |
5ee0fc14 | 200 | if (WebLibraryUrls.LIST_URL_METADATA.equals(uri)) { |
f433d153 | 201 | List<JSONObject> jsons = new ArrayList<JSONObject>(); |
d11fb35b | 202 | for (MetaData meta : metas(login)) { |
f433d153 NR |
203 | jsons.add(JsonIO.toJson(meta)); |
204 | } | |
205 | ||
206 | return newInputStreamResponse("application/json", | |
599b05c7 NR |
207 | new ByteArrayInputStream( |
208 | new JSONArray(jsons).toString().getBytes())); | |
f433d153 NR |
209 | } |
210 | ||
211 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
212 | NanoHTTPD.MIME_PLAINTEXT, null); | |
213 | } | |
214 | ||
f433d153 NR |
215 | // /story/luid/chapter/para <-- text/image |
216 | // /story/luid/cover <-- image | |
217 | // /story/luid/metadata <-- json | |
c5103223 | 218 | // /story/luid/json <-- json, whole chapter (no images) |
33d40b9f NR |
219 | @Override |
220 | protected Response getStoryPart(String uri, WLoginResult login) { | |
6673ec59 | 221 | String[] uriParts = uri.split("/"); |
f433d153 NR |
222 | int off = 2; |
223 | ||
6673ec59 | 224 | if (uriParts.length < off + 2) { |
f433d153 NR |
225 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, |
226 | NanoHTTPD.MIME_PLAINTEXT, null); | |
227 | } | |
228 | ||
6673ec59 NR |
229 | String luid = uriParts[off + 0]; |
230 | String chapterStr = uriParts[off + 1]; | |
231 | String imageStr = uriParts.length < off + 3 ? null : uriParts[off + 2]; | |
f433d153 NR |
232 | |
233 | // 1-based (0 = desc) | |
234 | int chapter = 0; | |
235 | if (chapterStr != null && !"cover".equals(chapterStr) | |
599b05c7 NR |
236 | && !"metadata".equals(chapterStr) |
237 | && !"json".equals(chapterStr)) { | |
f433d153 NR |
238 | try { |
239 | chapter = Integer.parseInt(chapterStr); | |
240 | if (chapter < 0) { | |
241 | throw new NumberFormatException(); | |
242 | } | |
243 | } catch (NumberFormatException e) { | |
244 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
245 | NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid"); | |
246 | } | |
247 | } | |
248 | ||
249 | // 1-based | |
250 | int paragraph = 1; | |
251 | if (imageStr != null) { | |
252 | try { | |
253 | paragraph = Integer.parseInt(imageStr); | |
254 | if (paragraph < 0) { | |
255 | throw new NumberFormatException(); | |
256 | } | |
257 | } catch (NumberFormatException e) { | |
258 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
259 | NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid"); | |
260 | } | |
261 | } | |
262 | ||
263 | String mimeType = NanoHTTPD.MIME_PLAINTEXT; | |
264 | InputStream in = null; | |
265 | try { | |
266 | if ("cover".equals(chapterStr)) { | |
6673ec59 | 267 | Image img = storyCover(luid, login); |
f433d153 NR |
268 | if (img != null) { |
269 | in = img.newInputStream(); | |
270 | } | |
3f468ac7 NR |
271 | // TODO: get correct image type |
272 | mimeType = "image/png"; | |
f433d153 | 273 | } else if ("metadata".equals(chapterStr)) { |
d11fb35b | 274 | MetaData meta = meta(luid, login); |
f433d153 NR |
275 | JSONObject json = JsonIO.toJson(meta); |
276 | mimeType = "application/json"; | |
277 | in = new ByteArrayInputStream(json.toString().getBytes()); | |
3fbc084c | 278 | } else if ("json".equals(chapterStr)) { |
d11fb35b | 279 | Story story = story(luid, login); |
c5103223 NR |
280 | JSONObject json = JsonIO.toJson(story); |
281 | mimeType = "application/json"; | |
282 | in = new ByteArrayInputStream(json.toString().getBytes()); | |
f433d153 | 283 | } else { |
d11fb35b | 284 | Story story = story(luid, login); |
f433d153 NR |
285 | if (story != null) { |
286 | if (chapter == 0) { | |
287 | StringBuilder builder = new StringBuilder(); | |
288 | for (Paragraph p : story.getMeta().getResume()) { | |
289 | if (builder.length() == 0) { | |
290 | builder.append("\n"); | |
291 | } | |
292 | builder.append(p.getContent()); | |
293 | } | |
294 | ||
599b05c7 NR |
295 | in = new ByteArrayInputStream( |
296 | builder.toString().getBytes("utf-8")); | |
f433d153 NR |
297 | } else { |
298 | Paragraph para = story.getChapters().get(chapter - 1) | |
299 | .getParagraphs().get(paragraph - 1); | |
300 | Image img = para.getContentImage(); | |
301 | if (para.getType() == ParagraphType.IMAGE) { | |
302 | // TODO: get correct image type | |
303 | mimeType = "image/png"; | |
304 | in = img.newInputStream(); | |
305 | } else { | |
599b05c7 NR |
306 | in = new ByteArrayInputStream( |
307 | para.getContent().getBytes("utf-8")); | |
f433d153 NR |
308 | } |
309 | } | |
310 | } | |
311 | } | |
312 | } catch (IndexOutOfBoundsException e) { | |
313 | return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, | |
314 | NanoHTTPD.MIME_PLAINTEXT, | |
315 | "Chapter or paragraph does not exist"); | |
316 | } catch (IOException e) { | |
317 | Instance.getInstance().getTraceHandler() | |
318 | .error(new IOException("Cannot get image: " + uri, e)); | |
319 | return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, | |
320 | NanoHTTPD.MIME_PLAINTEXT, "Error when processing request"); | |
321 | } | |
322 | ||
323 | return newInputStreamResponse(mimeType, in); | |
324 | } | |
325 | ||
089e354e NR |
326 | // /story/luid/source |
327 | // /story/luid/title | |
328 | // /story/luid/author | |
329 | @Override | |
330 | protected Response setStoryPart(String uri, String value, | |
331 | WLoginResult login) throws IOException { | |
332 | String[] uriParts = uri.split("/"); | |
333 | int off = 2; // "" and "story" | |
334 | ||
335 | if (uriParts.length < off + 2) { | |
336 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
337 | NanoHTTPD.MIME_PLAINTEXT, "Invalid story part request"); | |
338 | } | |
339 | ||
a1226ce0 NR |
340 | if (!login.isRw()) { |
341 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
342 | NanoHTTPD.MIME_PLAINTEXT, "SET story part not allowed"); | |
343 | } | |
09c2396e NR |
344 | |
345 | if (exiting) { | |
346 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
347 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
348 | } | |
a1226ce0 | 349 | |
089e354e NR |
350 | String luid = uriParts[off + 0]; |
351 | String type = uriParts[off + 1]; | |
352 | ||
353 | if (!Arrays.asList("source", "title", "author").contains(type)) { | |
354 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
355 | NanoHTTPD.MIME_PLAINTEXT, | |
356 | "Invalid SET story part: " + type); | |
357 | } | |
358 | ||
359 | if (meta(luid, login) != null) { | |
360 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
361 | if ("source".equals(type)) { | |
362 | lib.changeSource(luid, value, null); | |
363 | } else if ("title".equals(type)) { | |
364 | lib.changeTitle(luid, value, null); | |
365 | } else if ("author".equals(type)) { | |
366 | lib.changeAuthor(luid, value, null); | |
367 | } | |
368 | } | |
369 | ||
370 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
371 | } | |
372 | ||
6673ec59 NR |
373 | @Override |
374 | protected Response getCover(String uri, WLoginResult login) | |
375 | throws IOException { | |
376 | String[] uriParts = uri.split("/"); | |
377 | int off = 2; // "" and "cover" | |
378 | ||
379 | if (uriParts.length < off + 2) { | |
380 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
381 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
382 | } | |
383 | ||
384 | String type = uriParts[off + 0]; | |
385 | String id = uriParts[off + 1]; | |
386 | ||
387 | InputStream in = null; | |
388 | ||
e4b1b70c | 389 | if ("story".equals(type)) { |
6673ec59 NR |
390 | Image img = storyCover(id, login); |
391 | if (img != null) { | |
392 | in = img.newInputStream(); | |
393 | } | |
e4b1b70c NR |
394 | } else if ("source".equals(type)) { |
395 | Image img = sourceCover(id, login); | |
6673ec59 NR |
396 | if (img != null) { |
397 | in = img.newInputStream(); | |
398 | } | |
e4b1b70c NR |
399 | } else if ("author".equals(type)) { |
400 | Image img = authorCover(id, login); | |
6673ec59 NR |
401 | if (img != null) { |
402 | in = img.newInputStream(); | |
403 | } | |
404 | } else { | |
405 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
e4b1b70c NR |
406 | NanoHTTPD.MIME_PLAINTEXT, |
407 | "Invalid GET cover type: " + type); | |
6673ec59 NR |
408 | } |
409 | ||
410 | // TODO: get correct image type | |
411 | return newInputStreamResponse("image/png", in); | |
412 | } | |
413 | ||
e4b1b70c NR |
414 | @Override |
415 | protected Response setCover(String uri, String luid, WLoginResult login) | |
416 | throws IOException { | |
417 | String[] uriParts = uri.split("/"); | |
418 | int off = 2; // "" and "cover" | |
419 | ||
420 | if (uriParts.length < off + 2) { | |
421 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
422 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
423 | } | |
424 | ||
a1226ce0 NR |
425 | if (!login.isRw()) { |
426 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
427 | NanoHTTPD.MIME_PLAINTEXT, "Cover request not allowed"); | |
428 | } | |
09c2396e NR |
429 | |
430 | if (exiting) { | |
431 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
432 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
433 | } | |
a1226ce0 | 434 | |
e4b1b70c NR |
435 | String type = uriParts[off + 0]; |
436 | String id = uriParts[off + 1]; | |
437 | ||
438 | if ("source".equals(type)) { | |
439 | sourceCover(id, login, luid); | |
440 | } else if ("author".equals(type)) { | |
441 | authorCover(id, login, luid); | |
442 | } else { | |
443 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
444 | NanoHTTPD.MIME_PLAINTEXT, | |
445 | "Invalid SET cover type: " + type); | |
446 | } | |
447 | ||
448 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
449 | } | |
450 | ||
f70bcacf NR |
451 | @Override |
452 | protected Response imprt(String uri, String urlStr, WLoginResult login) | |
453 | throws IOException { | |
454 | final BasicLibrary lib = Instance.getInstance().getLibrary(); | |
455 | ||
a1226ce0 NR |
456 | if (!login.isRw()) { |
457 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
458 | NanoHTTPD.MIME_PLAINTEXT, "Import not allowed"); | |
459 | } | |
09c2396e NR |
460 | |
461 | if (exiting) { | |
462 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
463 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
464 | } | |
a1226ce0 | 465 | |
f70bcacf NR |
466 | final URL url = new URL(urlStr); |
467 | final Progress pg = new Progress(); | |
468 | final String luid = lib.getNextId(); | |
469 | ||
470 | synchronized (imprts) { | |
471 | imprts.put(luid, pg); | |
472 | } | |
473 | ||
474 | new Thread(new Runnable() { | |
475 | @Override | |
476 | public void run() { | |
477 | try { | |
acbec0d2 | 478 | lib.imprt(url, luid, pg); |
f70bcacf NR |
479 | } catch (IOException e) { |
480 | Instance.getInstance().getTraceHandler().error(e); | |
481 | } finally { | |
482 | synchronized (imprts) { | |
483 | imprts.remove(luid); | |
484 | } | |
485 | } | |
486 | } | |
487 | }, "Import story: " + urlStr).start(); | |
488 | ||
f70bcacf NR |
489 | return NanoHTTPD.newFixedLengthResponse(Status.OK, |
490 | NanoHTTPD.MIME_PLAINTEXT, luid); | |
491 | } | |
492 | ||
493 | @Override | |
494 | protected Response imprtProgress(String uri, WLoginResult login) { | |
495 | String[] uriParts = uri.split("/"); | |
496 | int off = 2; // "" and "import" | |
497 | ||
498 | if (uriParts.length < off + 1) { | |
499 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
500 | NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); | |
501 | } | |
502 | ||
503 | String luid = uriParts[off + 0]; | |
504 | ||
505 | Progress pg = null; | |
506 | synchronized (imprts) { | |
507 | pg = imprts.get(luid); | |
508 | } | |
509 | if (pg != null) { | |
510 | return NanoHTTPD.newFixedLengthResponse(Status.OK, | |
511 | "application/json", JsonIO.toJson(pg).toString()); | |
512 | } | |
513 | ||
514 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
515 | } | |
516 | ||
a1226ce0 NR |
517 | @Override |
518 | protected Response delete(String uri, WLoginResult login) | |
519 | throws IOException { | |
520 | String[] uriParts = uri.split("/"); | |
521 | int off = 2; // "" and "delete" | |
522 | ||
523 | if (uriParts.length < off + 1) { | |
524 | return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, | |
525 | NanoHTTPD.MIME_PLAINTEXT, "Invalid delete request"); | |
526 | } | |
527 | ||
528 | if (!login.isRw()) { | |
529 | return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, | |
530 | NanoHTTPD.MIME_PLAINTEXT, "Delete not allowed"); | |
531 | } | |
09c2396e NR |
532 | |
533 | if (exiting) { | |
534 | return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, | |
535 | NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); | |
536 | } | |
a1226ce0 NR |
537 | |
538 | String luid = uriParts[off + 0]; | |
539 | ||
540 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
541 | lib.delete(luid); | |
542 | ||
543 | return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); | |
544 | } | |
545 | ||
33d40b9f NR |
546 | @Override |
547 | protected List<MetaData> metas(WLoginResult login) throws IOException { | |
d11fb35b NR |
548 | BasicLibrary lib = Instance.getInstance().getLibrary(); |
549 | List<MetaData> metas = new ArrayList<MetaData>(); | |
550 | for (MetaData meta : lib.getList().getMetas()) { | |
551 | if (isAllowed(meta, login)) { | |
552 | metas.add(meta); | |
553 | } | |
554 | } | |
555 | ||
556 | return metas; | |
557 | } | |
558 | ||
f433d153 | 559 | // NULL if not whitelist OK or if not found |
33d40b9f NR |
560 | @Override |
561 | protected Story story(String luid, WLoginResult login) throws IOException { | |
f433d153 NR |
562 | synchronized (storyCache) { |
563 | if (storyCache.containsKey(luid)) { | |
564 | Story story = storyCache.get(luid); | |
d11fb35b | 565 | if (!isAllowed(story.getMeta(), login)) |
f433d153 | 566 | return null; |
f433d153 NR |
567 | |
568 | return story; | |
569 | } | |
570 | } | |
571 | ||
572 | Story story = null; | |
d11fb35b | 573 | MetaData meta = meta(luid, login); |
f433d153 NR |
574 | if (meta != null) { |
575 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
576 | story = lib.getStory(luid, null); | |
577 | long size = sizeOf(story); | |
578 | ||
579 | synchronized (storyCache) { | |
580 | // Could have been added by another request | |
581 | if (!storyCache.containsKey(luid)) { | |
582 | while (!storyCacheOrder.isEmpty() | |
583 | && storyCacheSize + size > maxStoryCacheSize) { | |
584 | String oldestLuid = storyCacheOrder.removeFirst(); | |
585 | Story oldestStory = storyCache.remove(oldestLuid); | |
586 | maxStoryCacheSize -= sizeOf(oldestStory); | |
587 | } | |
588 | ||
589 | storyCacheOrder.add(luid); | |
590 | storyCache.put(luid, story); | |
591 | } | |
592 | } | |
593 | } | |
594 | ||
595 | return story; | |
596 | } | |
597 | ||
33d40b9f NR |
598 | private MetaData meta(String luid, WLoginResult login) throws IOException { |
599 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
600 | MetaData meta = lib.getInfo(luid); | |
601 | if (!isAllowed(meta, login)) | |
602 | return null; | |
f433d153 | 603 | |
33d40b9f | 604 | return meta; |
f433d153 NR |
605 | } |
606 | ||
6673ec59 NR |
607 | private Image storyCover(String luid, WLoginResult login) |
608 | throws IOException { | |
33d40b9f NR |
609 | MetaData meta = meta(luid, login); |
610 | if (meta != null) { | |
611 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
612 | return lib.getCover(meta.getLuid()); | |
f433d153 | 613 | } |
f433d153 | 614 | |
33d40b9f | 615 | return null; |
f433d153 NR |
616 | } |
617 | ||
6673ec59 NR |
618 | private Image authorCover(String author, WLoginResult login) |
619 | throws IOException { | |
620 | Image img = null; | |
621 | ||
622 | List<MetaData> metas = new MetaResultList(metas(login)).filter(null, | |
623 | author, null); | |
624 | if (metas.size() > 0) { | |
625 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
626 | img = lib.getCustomAuthorCover(author); | |
627 | if (img == null) | |
628 | img = lib.getCover(metas.get(0).getLuid()); | |
629 | } | |
630 | ||
631 | return img; | |
632 | ||
633 | } | |
634 | ||
e4b1b70c NR |
635 | private void authorCover(String author, WLoginResult login, String luid) |
636 | throws IOException { | |
637 | if (meta(luid, login) != null) { | |
638 | List<MetaData> metas = new MetaResultList(metas(login)).filter(null, | |
639 | author, null); | |
640 | if (metas.size() > 0) { | |
641 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
642 | lib.setAuthorCover(author, luid); | |
643 | } | |
644 | } | |
645 | } | |
646 | ||
6673ec59 NR |
647 | private Image sourceCover(String source, WLoginResult login) |
648 | throws IOException { | |
649 | Image img = null; | |
650 | ||
651 | List<MetaData> metas = new MetaResultList(metas(login)).filter(source, | |
652 | null, null); | |
653 | if (metas.size() > 0) { | |
654 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
655 | img = lib.getCustomSourceCover(source); | |
656 | if (img == null) | |
657 | img = lib.getCover(metas.get(0).getLuid()); | |
658 | } | |
659 | ||
660 | return img; | |
661 | } | |
662 | ||
e4b1b70c NR |
663 | private void sourceCover(String source, WLoginResult login, String luid) |
664 | throws IOException { | |
665 | if (meta(luid, login) != null) { | |
666 | List<MetaData> metas = new MetaResultList(metas(login)) | |
667 | .filter(source, null, null); | |
668 | if (metas.size() > 0) { | |
669 | BasicLibrary lib = Instance.getInstance().getLibrary(); | |
670 | lib.setSourceCover(source, luid); | |
671 | } | |
672 | } | |
673 | } | |
674 | ||
33d40b9f | 675 | private boolean isAllowed(MetaData meta, WLoginResult login) { |
5c4ce687 NR |
676 | MetaResultList one = new MetaResultList(Arrays.asList(meta)); |
677 | if (login.isWl() && !whitelist.isEmpty()) { | |
678 | if (one.filter(whitelist, null, null).isEmpty()) { | |
679 | return false; | |
680 | } | |
f433d153 | 681 | } |
5c4ce687 NR |
682 | if (login.isBl() && !blacklist.isEmpty()) { |
683 | if (!one.filter(blacklist, null, null).isEmpty()) { | |
684 | return false; | |
685 | } | |
599b05c7 NR |
686 | } |
687 | ||
33d40b9f | 688 | return true; |
599b05c7 NR |
689 | } |
690 | ||
33d40b9f NR |
691 | private long sizeOf(Story story) { |
692 | long size = 0; | |
693 | for (Chapter chap : story) { | |
694 | for (Paragraph para : chap) { | |
695 | if (para.getType() == ParagraphType.IMAGE) { | |
696 | size += para.getContentImage().getSize(); | |
697 | } else { | |
698 | size += para.getContent().length(); | |
699 | } | |
700 | } | |
6b89e45c NR |
701 | } |
702 | ||
33d40b9f | 703 | return size; |
6b89e45c NR |
704 | } |
705 | ||
3fbc084c NR |
706 | public static void main(String[] args) throws IOException { |
707 | Instance.init(); | |
708 | WebLibraryServer web = new WebLibraryServer(false); | |
709 | web.run(); | |
710 | } | |
f433d153 | 711 | } |