Commit | Line | Data |
---|---|---|
e42573a0 | 1 | package be.nikiroo.fanfix.library; |
68e2c6d2 | 2 | |
68e2c6d2 NR |
3 | import java.io.File; |
4 | import java.io.IOException; | |
5 | import java.net.URL; | |
fd1d31c2 | 6 | import java.net.UnknownHostException; |
68e2c6d2 NR |
7 | import java.util.ArrayList; |
8 | import java.util.Collections; | |
9 | import java.util.List; | |
c1b93db3 | 10 | import java.util.Map; |
c1b93db3 | 11 | import java.util.TreeMap; |
68e2c6d2 | 12 | |
e42573a0 | 13 | import be.nikiroo.fanfix.Instance; |
68e2c6d2 NR |
14 | import be.nikiroo.fanfix.data.MetaData; |
15 | import be.nikiroo.fanfix.data.Story; | |
16 | import be.nikiroo.fanfix.output.BasicOutput; | |
17 | import be.nikiroo.fanfix.output.BasicOutput.OutputType; | |
18 | import be.nikiroo.fanfix.supported.BasicSupport; | |
0ffa4754 | 19 | import be.nikiroo.fanfix.supported.SupportType; |
16a81ef7 | 20 | import be.nikiroo.utils.Image; |
68e2c6d2 | 21 | import be.nikiroo.utils.Progress; |
5f42f329 | 22 | import be.nikiroo.utils.StringUtils; |
68e2c6d2 NR |
23 | |
24 | /** | |
25 | * Manage a library of Stories: import, export, list, modify. | |
26 | * <p> | |
27 | * Each {@link Story} object will be associated with a (local to the library) | |
28 | * unique ID, the LUID, which will be used to identify the {@link Story}. | |
29 | * <p> | |
30 | * Most of the {@link BasicLibrary} functions work on a partial (cover | |
31 | * <b>MAY</b> not be included) {@link MetaData} object. | |
32 | * | |
33 | * @author niki | |
34 | */ | |
35 | abstract public class BasicLibrary { | |
e6249b0f NR |
36 | /** |
37 | * A {@link BasicLibrary} status. | |
38 | * | |
39 | * @author niki | |
40 | */ | |
41 | public enum Status { | |
0bb51c9c NR |
42 | /** The library is ready and r/w. */ |
43 | READ_WRITE, | |
44 | /** The library is ready, but read-only. */ | |
45 | READ_ONLY, | |
e6249b0f NR |
46 | /** The library is invalid (not correctly set up). */ |
47 | INVALID, | |
48 | /** You are not allowed to access this library. */ | |
99206a39 | 49 | UNAUTHORIZED, |
e6249b0f | 50 | /** The library is currently out of commission. */ |
0bb51c9c NR |
51 | UNAVAILABLE; |
52 | ||
53 | /** | |
54 | * The library is available (you can query it). | |
55 | * <p> | |
56 | * It does <b>not</b> specify if it is read-only or not. | |
57 | * | |
58 | * @return TRUE if it is | |
59 | */ | |
60 | public boolean isReady() { | |
61 | return (this == READ_WRITE || this == READ_ONLY); | |
62 | } | |
63 | ||
64 | /** | |
65 | * This library can be modified (= you are allowed to modify it). | |
66 | * | |
67 | * @return TRUE if it is | |
68 | */ | |
69 | public boolean isWritable() { | |
70 | return (this == READ_WRITE); | |
71 | } | |
e6249b0f NR |
72 | } |
73 | ||
99ccbdf6 NR |
74 | /** |
75 | * Return a name for this library (the UI may display this). | |
76 | * <p> | |
77 | * Must not be NULL. | |
78 | * | |
79 | * @return the name, or an empty {@link String} if none | |
80 | */ | |
81 | public String getLibraryName() { | |
82 | return ""; | |
83 | } | |
84 | ||
e6249b0f NR |
85 | /** |
86 | * The library status. | |
87 | * | |
88 | * @return the current status | |
89 | */ | |
90 | public Status getStatus() { | |
0bb51c9c | 91 | return Status.READ_WRITE; |
e6249b0f NR |
92 | } |
93 | ||
68e2c6d2 NR |
94 | /** |
95 | * Retrieve the main {@link File} corresponding to the given {@link Story}, | |
96 | * which can be passed to an external reader or instance. | |
97 | * <p> | |
98 | * Do <b>NOT</b> alter this file. | |
99 | * | |
100 | * @param luid | |
dc919036 | 101 | * the Library UID of the story, can be NULL |
ff05b828 NR |
102 | * @param pg |
103 | * the optional {@link Progress} | |
68e2c6d2 NR |
104 | * |
105 | * @return the corresponding {@link Story} | |
0bb51c9c NR |
106 | * |
107 | * @throws IOException | |
108 | * in case of IOException | |
68e2c6d2 | 109 | */ |
0bb51c9c | 110 | public abstract File getFile(String luid, Progress pg) throws IOException; |
68e2c6d2 NR |
111 | |
112 | /** | |
113 | * Return the cover image associated to this story. | |
114 | * | |
115 | * @param luid | |
116 | * the Library UID of the story | |
117 | * | |
118 | * @return the cover image | |
0bb51c9c NR |
119 | * |
120 | * @throws IOException | |
121 | * in case of IOException | |
68e2c6d2 | 122 | */ |
0bb51c9c | 123 | public abstract Image getCover(String luid) throws IOException; |
68e2c6d2 | 124 | |
1f2a7d5f | 125 | // TODO: ensure it is the main used interface |
dc919036 | 126 | public MetaResultList getList(Progress pg) throws IOException { |
1f2a7d5f NR |
127 | return new MetaResultList(getMetas(pg)); |
128 | } | |
dc919036 NR |
129 | |
130 | // TODO: make something for (normal and custom) not-story covers | |
131 | ||
14b57448 NR |
132 | /** |
133 | * Return the cover image associated to this source. | |
134 | * <p> | |
e1de8087 NR |
135 | * By default, return the custom cover if any, and if not, return the cover |
136 | * of the first story with this source. | |
14b57448 NR |
137 | * |
138 | * @param source | |
139 | * the source | |
140 | * | |
141 | * @return the cover image or NULL | |
0bb51c9c NR |
142 | * |
143 | * @throws IOException | |
144 | * in case of IOException | |
14b57448 | 145 | */ |
0bb51c9c | 146 | public Image getSourceCover(String source) throws IOException { |
e1de8087 NR |
147 | Image custom = getCustomSourceCover(source); |
148 | if (custom != null) { | |
149 | return custom; | |
150 | } | |
151 | ||
ef98466f | 152 | List<MetaData> metas = getList().filter(source, null, null); |
14b57448 NR |
153 | if (metas.size() > 0) { |
154 | return getCover(metas.get(0).getLuid()); | |
155 | } | |
156 | ||
157 | return null; | |
158 | } | |
159 | ||
3989dfc5 NR |
160 | /** |
161 | * Return the cover image associated to this author. | |
162 | * <p> | |
163 | * By default, return the custom cover if any, and if not, return the cover | |
164 | * of the first story with this author. | |
165 | * | |
166 | * @param author | |
167 | * the author | |
168 | * | |
169 | * @return the cover image or NULL | |
0bb51c9c NR |
170 | * |
171 | * @throws IOException | |
172 | * in case of IOException | |
3989dfc5 | 173 | */ |
0bb51c9c | 174 | public Image getAuthorCover(String author) throws IOException { |
3989dfc5 NR |
175 | Image custom = getCustomAuthorCover(author); |
176 | if (custom != null) { | |
177 | return custom; | |
178 | } | |
179 | ||
ef98466f | 180 | List<MetaData> metas = getList().filter(null, author, null); |
3989dfc5 NR |
181 | if (metas.size() > 0) { |
182 | return getCover(metas.get(0).getLuid()); | |
183 | } | |
184 | ||
185 | return null; | |
186 | } | |
187 | ||
e1de8087 NR |
188 | /** |
189 | * Return the custom cover image associated to this source. | |
190 | * <p> | |
191 | * By default, return NULL. | |
192 | * | |
193 | * @param source | |
194 | * the source to look for | |
195 | * | |
196 | * @return the custom cover or NULL if none | |
0bb51c9c NR |
197 | * |
198 | * @throws IOException | |
199 | * in case of IOException | |
e1de8087 | 200 | */ |
0c7a6637 NR |
201 | @SuppressWarnings("unused") |
202 | public Image getCustomSourceCover(String source) throws IOException { | |
e1de8087 NR |
203 | return null; |
204 | } | |
205 | ||
14b57448 | 206 | /** |
3989dfc5 NR |
207 | * Return the custom cover image associated to this author. |
208 | * <p> | |
209 | * By default, return NULL. | |
210 | * | |
211 | * @param author | |
212 | * the author to look for | |
213 | * | |
214 | * @return the custom cover or NULL if none | |
0bb51c9c NR |
215 | * |
216 | * @throws IOException | |
217 | * in case of IOException | |
3989dfc5 | 218 | */ |
0c7a6637 NR |
219 | @SuppressWarnings("unused") |
220 | public Image getCustomAuthorCover(String author) throws IOException { | |
3989dfc5 NR |
221 | return null; |
222 | } | |
223 | ||
224 | /** | |
225 | * Set the source cover to the given story cover. | |
14b57448 NR |
226 | * |
227 | * @param source | |
228 | * the source to change | |
229 | * @param luid | |
230 | * the story LUID | |
0bb51c9c NR |
231 | * |
232 | * @throws IOException | |
233 | * in case of IOException | |
14b57448 | 234 | */ |
0bb51c9c NR |
235 | public abstract void setSourceCover(String source, String luid) |
236 | throws IOException; | |
14b57448 | 237 | |
3989dfc5 NR |
238 | /** |
239 | * Set the author cover to the given story cover. | |
240 | * | |
d4449e96 | 241 | * @param author |
3989dfc5 NR |
242 | * the author to change |
243 | * @param luid | |
244 | * the story LUID | |
0bb51c9c NR |
245 | * |
246 | * @throws IOException | |
247 | * in case of IOException | |
3989dfc5 | 248 | */ |
0bb51c9c NR |
249 | public abstract void setAuthorCover(String author, String luid) |
250 | throws IOException; | |
3989dfc5 | 251 | |
68e2c6d2 NR |
252 | /** |
253 | * Return the list of stories (represented by their {@link MetaData}, which | |
254 | * <b>MAY</b> not have the cover included). | |
1f2a7d5f NR |
255 | * <p> |
256 | * The returned list <b>MUST</b> be a copy, not the original one. | |
68e2c6d2 NR |
257 | * |
258 | * @param pg | |
259 | * the optional {@link Progress} | |
260 | * | |
261 | * @return the list (can be empty but not NULL) | |
0bb51c9c NR |
262 | * |
263 | * @throws IOException | |
264 | * in case of IOException | |
68e2c6d2 | 265 | */ |
0bb51c9c | 266 | protected abstract List<MetaData> getMetas(Progress pg) throws IOException; |
68e2c6d2 NR |
267 | |
268 | /** | |
269 | * Invalidate the {@link Story} cache (when the content should be re-read | |
270 | * because it was changed). | |
271 | */ | |
c8d48938 NR |
272 | protected void invalidateInfo() { |
273 | invalidateInfo(null); | |
e272f05f NR |
274 | } |
275 | ||
276 | /** | |
efa3c511 NR |
277 | * Invalidate the {@link Story} cache (when the content is removed). |
278 | * <p> | |
279 | * All the cache can be deleted if NULL is passed as meta. | |
e272f05f NR |
280 | * |
281 | * @param luid | |
efa3c511 | 282 | * the LUID of the {@link Story} to clear from the cache, or NULL |
e272f05f NR |
283 | * for all stories |
284 | */ | |
c8d48938 | 285 | protected abstract void invalidateInfo(String luid); |
efa3c511 NR |
286 | |
287 | /** | |
288 | * Invalidate the {@link Story} cache (when the content has changed, but we | |
289 | * already have it) with the new given meta. | |
290 | * | |
291 | * @param meta | |
292 | * the {@link Story} to clear from the cache | |
0bb51c9c NR |
293 | * |
294 | * @throws IOException | |
295 | * in case of IOException | |
efa3c511 | 296 | */ |
0bb51c9c | 297 | protected abstract void updateInfo(MetaData meta) throws IOException; |
68e2c6d2 NR |
298 | |
299 | /** | |
300 | * Return the next LUID that can be used. | |
301 | * | |
302 | * @return the next luid | |
303 | */ | |
304 | protected abstract int getNextId(); | |
305 | ||
306 | /** | |
307 | * Delete the target {@link Story}. | |
308 | * | |
309 | * @param luid | |
310 | * the LUID of the {@link Story} | |
311 | * | |
312 | * @throws IOException | |
313 | * in case of I/O error or if the {@link Story} wa not found | |
314 | */ | |
315 | protected abstract void doDelete(String luid) throws IOException; | |
316 | ||
317 | /** | |
318 | * Actually save the story to the back-end. | |
319 | * | |
320 | * @param story | |
321 | * the {@link Story} to save | |
322 | * @param pg | |
323 | * the optional {@link Progress} | |
324 | * | |
325 | * @return the saved {@link Story} (which may have changed, especially | |
326 | * regarding the {@link MetaData}) | |
327 | * | |
328 | * @throws IOException | |
329 | * in case of I/O error | |
330 | */ | |
331 | protected abstract Story doSave(Story story, Progress pg) | |
332 | throws IOException; | |
333 | ||
334 | /** | |
e6249b0f | 335 | * Refresh the {@link BasicLibrary}, that is, make sure all metas are |
68e2c6d2 NR |
336 | * loaded. |
337 | * | |
68e2c6d2 NR |
338 | * @param pg |
339 | * the optional progress reporter | |
340 | */ | |
dc919036 | 341 | public void refresh(Progress pg) { |
0bb51c9c NR |
342 | try { |
343 | getMetas(pg); | |
344 | } catch (IOException e) { | |
345 | // We will let it fail later | |
346 | } | |
68e2c6d2 | 347 | } |
dc919036 | 348 | |
4452446c NR |
349 | /** |
350 | * Check if the {@link Story} denoted by this Library UID is present in the | |
541f433a | 351 | * cache (if we have no cache, we default to </tt>true</tt>). |
4452446c NR |
352 | * |
353 | * @param luid | |
354 | * the Library UID | |
355 | * | |
356 | * @return TRUE if it is | |
357 | */ | |
541f433a | 358 | public boolean isCached(@SuppressWarnings("unused") String luid) { |
4452446c NR |
359 | // By default, everything is cached |
360 | return true; | |
361 | } | |
dc919036 | 362 | |
4452446c NR |
363 | /** |
364 | * Clear the {@link Story} from the cache, if needed. | |
365 | * <p> | |
366 | * The next time we try to retrieve the {@link Story}, it may be required to | |
367 | * cache it again. | |
368 | * | |
369 | * @param luid | |
370 | * the story to clear | |
371 | * | |
372 | * @throws IOException | |
373 | * in case of I/O error | |
374 | */ | |
541f433a | 375 | @SuppressWarnings("unused") |
4452446c NR |
376 | public void clearFromCache(String luid) throws IOException { |
377 | // By default, this is a noop. | |
378 | } | |
68e2c6d2 NR |
379 | |
380 | /** | |
59819600 NR |
381 | * @deprecated please use {@link BasicLibrary#getList()} and |
382 | * {@link MetaResultList#getSources()} instead. | |
68e2c6d2 | 383 | */ |
59819600 | 384 | @Deprecated |
dc919036 | 385 | public List<String> getSources() throws IOException { |
59819600 | 386 | return getList().getSources(); |
68e2c6d2 NR |
387 | } |
388 | ||
c1b93db3 | 389 | /** |
59819600 NR |
390 | * @deprecated please use {@link BasicLibrary#getList()} and |
391 | * {@link MetaResultList#getSourcesGrouped()} instead. | |
c1b93db3 | 392 | */ |
59819600 | 393 | @Deprecated |
dc919036 | 394 | public Map<String, List<String>> getSourcesGrouped() throws IOException { |
59819600 | 395 | return getList().getSourcesGrouped(); |
c1b93db3 NR |
396 | } |
397 | ||
68e2c6d2 | 398 | /** |
59819600 NR |
399 | * @deprecated please use {@link BasicLibrary#getList()} and |
400 | * {@link MetaResultList#getAuthors()} instead. | |
68e2c6d2 | 401 | */ |
59819600 | 402 | @Deprecated |
dc919036 | 403 | public List<String> getAuthors() throws IOException { |
59819600 | 404 | return getList().getAuthors(); |
68e2c6d2 NR |
405 | } |
406 | ||
5f42f329 | 407 | /** |
59819600 NR |
408 | * @deprecated please use {@link BasicLibrary#getList()} and |
409 | * {@link MetaResultList#getAuthorsGrouped()} instead. | |
5f42f329 | 410 | */ |
0bb51c9c | 411 | public Map<String, List<String>> getAuthorsGrouped() throws IOException { |
59819600 | 412 | return getList().getAuthorsGrouped(); |
5f42f329 NR |
413 | } |
414 | ||
68e2c6d2 NR |
415 | /** |
416 | * List all the stories in the {@link BasicLibrary}. | |
417 | * <p> | |
3a0605e6 | 418 | * Cover images <b>MAYBE</b> not included. |
68e2c6d2 NR |
419 | * |
420 | * @return the stories | |
0bb51c9c NR |
421 | * |
422 | * @throws IOException | |
423 | * in case of IOException | |
68e2c6d2 | 424 | */ |
ef98466f NR |
425 | public MetaResultList getList() throws IOException { |
426 | return getList(null); | |
68e2c6d2 NR |
427 | } |
428 | ||
429 | /** | |
430 | * Retrieve a {@link MetaData} corresponding to the given {@link Story}, | |
431 | * cover image <b>MAY</b> not be included. | |
432 | * | |
433 | * @param luid | |
dc919036 | 434 | * the Library UID of the story, can be NULL |
68e2c6d2 | 435 | * |
dc919036 | 436 | * @return the corresponding {@link Story} or NULL if not found |
0bb51c9c NR |
437 | * |
438 | * @throws IOException | |
439 | * in case of IOException | |
68e2c6d2 | 440 | */ |
dc919036 | 441 | public MetaData getInfo(String luid) throws IOException { |
68e2c6d2 NR |
442 | if (luid != null) { |
443 | for (MetaData meta : getMetas(null)) { | |
444 | if (luid.equals(meta.getLuid())) { | |
445 | return meta; | |
446 | } | |
447 | } | |
448 | } | |
449 | ||
450 | return null; | |
451 | } | |
452 | ||
453 | /** | |
454 | * Retrieve a specific {@link Story}. | |
455 | * | |
456 | * @param luid | |
457 | * the Library UID of the story | |
458 | * @param pg | |
459 | * the optional progress reporter | |
460 | * | |
461 | * @return the corresponding {@link Story} or NULL if not found | |
0bb51c9c NR |
462 | * |
463 | * @throws IOException | |
464 | * in case of IOException | |
68e2c6d2 | 465 | */ |
59819600 | 466 | public Story getStory(String luid, Progress pg) throws IOException { |
60f72311 NR |
467 | Progress pgMetas = new Progress(); |
468 | Progress pgStory = new Progress(); | |
469 | if (pg != null) { | |
470 | pg.setMinMax(0, 100); | |
471 | pg.addProgress(pgMetas, 10); | |
472 | pg.addProgress(pgStory, 90); | |
473 | } | |
474 | ||
475 | MetaData meta = null; | |
476 | for (MetaData oneMeta : getMetas(pgMetas)) { | |
477 | if (oneMeta.getLuid().equals(luid)) { | |
478 | meta = oneMeta; | |
479 | break; | |
480 | } | |
481 | } | |
482 | ||
483 | pgMetas.done(); | |
484 | ||
485 | Story story = getStory(luid, meta, pgStory); | |
486 | pgStory.done(); | |
487 | ||
488 | return story; | |
489 | } | |
490 | ||
491 | /** | |
492 | * Retrieve a specific {@link Story}. | |
493 | * | |
494 | * @param luid | |
dc919036 NR |
495 | * the LUID of the story |
496 | * @param meta | |
60f72311 NR |
497 | * the meta of the story |
498 | * @param pg | |
499 | * the optional progress reporter | |
500 | * | |
501 | * @return the corresponding {@link Story} or NULL if not found | |
0bb51c9c NR |
502 | * |
503 | * @throws IOException | |
504 | * in case of IOException | |
60f72311 | 505 | */ |
dc919036 | 506 | public synchronized Story getStory(String luid, MetaData meta, Progress pg) |
0bb51c9c | 507 | throws IOException { |
60f72311 | 508 | |
68e2c6d2 NR |
509 | if (pg == null) { |
510 | pg = new Progress(); | |
511 | } | |
512 | ||
ff05b828 NR |
513 | Progress pgGet = new Progress(); |
514 | Progress pgProcess = new Progress(); | |
515 | ||
516 | pg.setMinMax(0, 2); | |
517 | pg.addProgress(pgGet, 1); | |
518 | pg.addProgress(pgProcess, 1); | |
519 | ||
68e2c6d2 | 520 | Story story = null; |
dc919036 NR |
521 | File file = null; |
522 | ||
523 | if (luid != null && meta != null) { | |
524 | file = getFile(luid, pgGet); | |
525 | } | |
526 | ||
60f72311 NR |
527 | pgGet.done(); |
528 | try { | |
dc919036 NR |
529 | if (file != null) { |
530 | SupportType type = SupportType.valueOfAllOkUC(meta.getType()); | |
531 | if (type == null) { | |
532 | throw new IOException("Unknown type: " + meta.getType()); | |
533 | } | |
534 | ||
535 | URL url = file.toURI().toURL(); | |
60f72311 NR |
536 | story = BasicSupport.getSupport(type, url) // |
537 | .process(pgProcess); | |
538 | ||
539 | // Because we do not want to clear the meta cache: | |
540 | meta.setCover(story.getMeta().getCover()); | |
541 | meta.setResume(story.getMeta().getResume()); | |
542 | story.setMeta(meta); | |
68e2c6d2 | 543 | } |
60f72311 | 544 | } catch (IOException e) { |
dc919036 NR |
545 | // We should not have not-supported files in the library |
546 | Instance.getInstance().getTraceHandler() | |
547 | .error(new IOException(String.format( | |
548 | "Cannot load file of type '%s' from library: %s", | |
549 | meta.getType(), file), e)); | |
60f72311 NR |
550 | } finally { |
551 | pgProcess.done(); | |
552 | pg.done(); | |
68e2c6d2 NR |
553 | } |
554 | ||
555 | return story; | |
556 | } | |
557 | ||
558 | /** | |
559 | * Import the {@link Story} at the given {@link URL} into the | |
560 | * {@link BasicLibrary}. | |
561 | * | |
562 | * @param url | |
563 | * the {@link URL} to import | |
564 | * @param pg | |
565 | * the optional progress reporter | |
566 | * | |
b6b65795 | 567 | * @return the imported Story {@link MetaData} |
68e2c6d2 | 568 | * |
fd1d31c2 NR |
569 | * @throws UnknownHostException |
570 | * if the host is not supported | |
68e2c6d2 NR |
571 | * @throws IOException |
572 | * in case of I/O error | |
573 | */ | |
b6b65795 | 574 | public MetaData imprt(URL url, Progress pg) throws IOException { |
9b863b20 NR |
575 | if (pg == null) |
576 | pg = new Progress(); | |
577 | ||
578 | pg.setMinMax(0, 1000); | |
579 | Progress pgProcess = new Progress(); | |
580 | Progress pgSave = new Progress(); | |
581 | pg.addProgress(pgProcess, 800); | |
582 | pg.addProgress(pgSave, 200); | |
583 | ||
68e2c6d2 NR |
584 | BasicSupport support = BasicSupport.getSupport(url); |
585 | if (support == null) { | |
fd1d31c2 | 586 | throw new UnknownHostException("" + url); |
68e2c6d2 NR |
587 | } |
588 | ||
9b863b20 NR |
589 | Story story = save(support.process(pgProcess), pgSave); |
590 | pg.done(); | |
591 | ||
b6b65795 | 592 | return story.getMeta(); |
68e2c6d2 NR |
593 | } |
594 | ||
b89dfb6e NR |
595 | /** |
596 | * Import the story from one library to another, and keep the same LUID. | |
597 | * | |
598 | * @param other | |
599 | * the other library to import from | |
600 | * @param luid | |
601 | * the Library UID | |
602 | * @param pg | |
603 | * the optional progress reporter | |
604 | * | |
605 | * @throws IOException | |
606 | * in case of I/O error | |
607 | */ | |
608 | public void imprt(BasicLibrary other, String luid, Progress pg) | |
609 | throws IOException { | |
610 | Progress pgGetStory = new Progress(); | |
611 | Progress pgSave = new Progress(); | |
612 | if (pg == null) { | |
613 | pg = new Progress(); | |
614 | } | |
615 | ||
616 | pg.setMinMax(0, 2); | |
617 | pg.addProgress(pgGetStory, 1); | |
618 | pg.addProgress(pgSave, 1); | |
619 | ||
620 | Story story = other.getStory(luid, pgGetStory); | |
621 | if (story != null) { | |
622 | story = this.save(story, luid, pgSave); | |
623 | pg.done(); | |
624 | } else { | |
625 | pg.done(); | |
626 | throw new IOException("Cannot find story in Library: " + luid); | |
627 | } | |
628 | } | |
629 | ||
68e2c6d2 NR |
630 | /** |
631 | * Export the {@link Story} to the given target in the given format. | |
632 | * | |
633 | * @param luid | |
634 | * the {@link Story} ID | |
635 | * @param type | |
636 | * the {@link OutputType} to transform it to | |
637 | * @param target | |
638 | * the target to save to | |
639 | * @param pg | |
640 | * the optional progress reporter | |
641 | * | |
642 | * @return the saved resource (the main saved {@link File}) | |
643 | * | |
644 | * @throws IOException | |
645 | * in case of I/O error | |
646 | */ | |
647 | public File export(String luid, OutputType type, String target, Progress pg) | |
648 | throws IOException { | |
649 | Progress pgGetStory = new Progress(); | |
650 | Progress pgOut = new Progress(); | |
651 | if (pg != null) { | |
652 | pg.setMax(2); | |
653 | pg.addProgress(pgGetStory, 1); | |
654 | pg.addProgress(pgOut, 1); | |
655 | } | |
656 | ||
925298fd | 657 | BasicOutput out = BasicOutput.getOutput(type, false, false); |
68e2c6d2 NR |
658 | if (out == null) { |
659 | throw new IOException("Output type not supported: " + type); | |
660 | } | |
661 | ||
662 | Story story = getStory(luid, pgGetStory); | |
663 | if (story == null) { | |
664 | throw new IOException("Cannot find story to export: " + luid); | |
665 | } | |
666 | ||
667 | return out.process(story, target, pgOut); | |
668 | } | |
669 | ||
670 | /** | |
671 | * Save a {@link Story} to the {@link BasicLibrary}. | |
672 | * | |
673 | * @param story | |
674 | * the {@link Story} to save | |
675 | * @param pg | |
676 | * the optional progress reporter | |
677 | * | |
678 | * @return the same {@link Story}, whose LUID may have changed | |
679 | * | |
680 | * @throws IOException | |
681 | * in case of I/O error | |
682 | */ | |
683 | public Story save(Story story, Progress pg) throws IOException { | |
684 | return save(story, null, pg); | |
685 | } | |
686 | ||
687 | /** | |
688 | * Save a {@link Story} to the {@link BasicLibrary} -- the LUID <b>must</b> | |
689 | * be correct, or NULL to get the next free one. | |
690 | * <p> | |
691 | * Will override any previous {@link Story} with the same LUID. | |
692 | * | |
693 | * @param story | |
694 | * the {@link Story} to save | |
695 | * @param luid | |
696 | * the <b>correct</b> LUID or NULL to get the next free one | |
697 | * @param pg | |
698 | * the optional progress reporter | |
699 | * | |
700 | * @return the same {@link Story}, whose LUID may have changed | |
701 | * | |
702 | * @throws IOException | |
703 | * in case of I/O error | |
704 | */ | |
705 | public synchronized Story save(Story story, String luid, Progress pg) | |
706 | throws IOException { | |
3b039231 NR |
707 | if (pg == null) { |
708 | pg = new Progress(); | |
709 | } | |
dc919036 NR |
710 | |
711 | Instance.getInstance().getTraceHandler().trace( | |
712 | this.getClass().getSimpleName() + ": saving story " + luid); | |
9e2fad36 | 713 | |
68e2c6d2 NR |
714 | // Do not change the original metadata, but change the original story |
715 | MetaData meta = story.getMeta().clone(); | |
716 | story.setMeta(meta); | |
717 | ||
3b039231 | 718 | pg.setName("Saving story"); |
dc919036 | 719 | |
68e2c6d2 NR |
720 | if (luid == null || luid.isEmpty()) { |
721 | meta.setLuid(String.format("%03d", getNextId())); | |
722 | } else { | |
723 | meta.setLuid(luid); | |
724 | } | |
725 | ||
e272f05f | 726 | if (luid != null && getInfo(luid) != null) { |
68e2c6d2 NR |
727 | delete(luid); |
728 | } | |
14b57448 | 729 | |
efa3c511 | 730 | story = doSave(story, pg); |
0bf8264e | 731 | |
efa3c511 | 732 | updateInfo(story.getMeta()); |
68e2c6d2 | 733 | |
d66deb8d | 734 | Instance.getInstance().getTraceHandler() |
dc919036 NR |
735 | .trace(this.getClass().getSimpleName() + ": story saved (" |
736 | + luid + ")"); | |
9e2fad36 | 737 | |
3b039231 NR |
738 | pg.setName(meta.getTitle()); |
739 | pg.done(); | |
68e2c6d2 NR |
740 | return story; |
741 | } | |
742 | ||
743 | /** | |
744 | * Delete the given {@link Story} from this {@link BasicLibrary}. | |
745 | * | |
746 | * @param luid | |
747 | * the LUID of the target {@link Story} | |
748 | * | |
749 | * @throws IOException | |
750 | * in case of I/O error | |
751 | */ | |
752 | public synchronized void delete(String luid) throws IOException { | |
dc919036 NR |
753 | Instance.getInstance().getTraceHandler().trace( |
754 | this.getClass().getSimpleName() + ": deleting story " + luid); | |
9e2fad36 | 755 | |
68e2c6d2 | 756 | doDelete(luid); |
c8d48938 | 757 | invalidateInfo(luid); |
9e2fad36 | 758 | |
d66deb8d | 759 | Instance.getInstance().getTraceHandler() |
dc919036 NR |
760 | .trace(this.getClass().getSimpleName() + ": story deleted (" |
761 | + luid + ")"); | |
68e2c6d2 NR |
762 | } |
763 | ||
764 | /** | |
765 | * Change the type (source) of the given {@link Story}. | |
766 | * | |
767 | * @param luid | |
768 | * the {@link Story} LUID | |
769 | * @param newSource | |
770 | * the new source | |
771 | * @param pg | |
772 | * the optional progress reporter | |
773 | * | |
774 | * @throws IOException | |
775 | * in case of I/O error or if the {@link Story} was not found | |
776 | */ | |
777 | public synchronized void changeSource(String luid, String newSource, | |
778 | Progress pg) throws IOException { | |
779 | MetaData meta = getInfo(luid); | |
780 | if (meta == null) { | |
781 | throw new IOException("Story not found: " + luid); | |
782 | } | |
783 | ||
c8d48938 NR |
784 | changeSTA(luid, newSource, meta.getTitle(), meta.getAuthor(), pg); |
785 | } | |
786 | ||
787 | /** | |
788 | * Change the title (name) of the given {@link Story}. | |
789 | * | |
790 | * @param luid | |
791 | * the {@link Story} LUID | |
792 | * @param newTitle | |
793 | * the new title | |
794 | * @param pg | |
795 | * the optional progress reporter | |
796 | * | |
797 | * @throws IOException | |
798 | * in case of I/O error or if the {@link Story} was not found | |
799 | */ | |
800 | public synchronized void changeTitle(String luid, String newTitle, | |
801 | Progress pg) throws IOException { | |
802 | MetaData meta = getInfo(luid); | |
803 | if (meta == null) { | |
804 | throw new IOException("Story not found: " + luid); | |
805 | } | |
806 | ||
807 | changeSTA(luid, meta.getSource(), newTitle, meta.getAuthor(), pg); | |
808 | } | |
809 | ||
810 | /** | |
811 | * Change the author of the given {@link Story}. | |
812 | * | |
813 | * @param luid | |
814 | * the {@link Story} LUID | |
815 | * @param newAuthor | |
816 | * the new author | |
817 | * @param pg | |
818 | * the optional progress reporter | |
819 | * | |
820 | * @throws IOException | |
821 | * in case of I/O error or if the {@link Story} was not found | |
822 | */ | |
823 | public synchronized void changeAuthor(String luid, String newAuthor, | |
824 | Progress pg) throws IOException { | |
825 | MetaData meta = getInfo(luid); | |
826 | if (meta == null) { | |
827 | throw new IOException("Story not found: " + luid); | |
828 | } | |
829 | ||
830 | changeSTA(luid, meta.getSource(), meta.getTitle(), newAuthor, pg); | |
831 | } | |
832 | ||
833 | /** | |
834 | * Change the Source, Title and Author of the {@link Story} in one single | |
835 | * go. | |
836 | * | |
837 | * @param luid | |
838 | * the {@link Story} LUID | |
839 | * @param newSource | |
840 | * the new source | |
841 | * @param newTitle | |
842 | * the new title | |
843 | * @param newAuthor | |
844 | * the new author | |
845 | * @param pg | |
846 | * the optional progress reporter | |
847 | * | |
848 | * @throws IOException | |
849 | * in case of I/O error or if the {@link Story} was not found | |
850 | */ | |
851 | protected synchronized void changeSTA(String luid, String newSource, | |
852 | String newTitle, String newAuthor, Progress pg) throws IOException { | |
853 | MetaData meta = getInfo(luid); | |
854 | if (meta == null) { | |
855 | throw new IOException("Story not found: " + luid); | |
856 | } | |
857 | ||
68e2c6d2 | 858 | meta.setSource(newSource); |
c8d48938 NR |
859 | meta.setTitle(newTitle); |
860 | meta.setAuthor(newAuthor); | |
68e2c6d2 NR |
861 | saveMeta(meta, pg); |
862 | } | |
863 | ||
864 | /** | |
865 | * Save back the current state of the {@link MetaData} (LUID <b>MUST NOT</b> | |
866 | * change) for this {@link Story}. | |
867 | * <p> | |
868 | * By default, delete the old {@link Story} then recreate a new | |
869 | * {@link Story}. | |
870 | * <p> | |
c8d48938 | 871 | * Note that this behaviour can lead to data loss in case of problems! |
68e2c6d2 NR |
872 | * |
873 | * @param meta | |
874 | * the new {@link MetaData} (LUID <b>MUST NOT</b> change) | |
875 | * @param pg | |
876 | * the optional {@link Progress} | |
877 | * | |
878 | * @throws IOException | |
879 | * in case of I/O error or if the {@link Story} was not found | |
880 | */ | |
881 | protected synchronized void saveMeta(MetaData meta, Progress pg) | |
882 | throws IOException { | |
883 | if (pg == null) { | |
884 | pg = new Progress(); | |
885 | } | |
886 | ||
887 | Progress pgGet = new Progress(); | |
888 | Progress pgSet = new Progress(); | |
889 | pg.addProgress(pgGet, 50); | |
890 | pg.addProgress(pgSet, 50); | |
891 | ||
892 | Story story = getStory(meta.getLuid(), pgGet); | |
893 | if (story == null) { | |
894 | throw new IOException("Story not found: " + meta.getLuid()); | |
895 | } | |
896 | ||
c8d48938 | 897 | // TODO: this is not safe! |
68e2c6d2 | 898 | delete(meta.getLuid()); |
68e2c6d2 NR |
899 | story.setMeta(meta); |
900 | save(story, meta.getLuid(), pgSet); | |
901 | ||
902 | pg.done(); | |
903 | } | |
904 | } |