Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / fanfix / library / BasicLibrary.java
CommitLineData
e42573a0 1package be.nikiroo.fanfix.library;
68e2c6d2 2
68e2c6d2
NR
3import java.io.File;
4import java.io.IOException;
5import java.net.URL;
fd1d31c2 6import java.net.UnknownHostException;
68e2c6d2
NR
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.List;
c1b93db3 10import java.util.Map;
c1b93db3 11import java.util.TreeMap;
68e2c6d2 12
e42573a0 13import be.nikiroo.fanfix.Instance;
68e2c6d2
NR
14import be.nikiroo.fanfix.data.MetaData;
15import be.nikiroo.fanfix.data.Story;
16import be.nikiroo.fanfix.output.BasicOutput;
17import be.nikiroo.fanfix.output.BasicOutput.OutputType;
18import be.nikiroo.fanfix.supported.BasicSupport;
0ffa4754 19import be.nikiroo.fanfix.supported.SupportType;
16a81ef7 20import be.nikiroo.utils.Image;
68e2c6d2 21import be.nikiroo.utils.Progress;
5f42f329 22import 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 */
35abstract 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}