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