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