1 package be
.nikiroo
.fanfix
.library
;
4 import java
.io
.IOException
;
6 import java
.net
.UnknownHostException
;
7 import java
.util
.ArrayList
;
8 import java
.util
.Collections
;
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
;
22 * Manage a library of Stories: import, export, list, modify.
24 * Each {@link Story} object will be associated with a (local to the library)
25 * unique ID, the LUID, which will be used to identify the {@link Story}.
27 * Most of the {@link BasicLibrary} functions work on a partial (cover
28 * <b>MAY</b> not be included) {@link MetaData} object.
32 abstract public class BasicLibrary
{
34 * A {@link BasicLibrary} status.
39 /** The library is ready. */
41 /** The library is invalid (not correctly set up). */
43 /** You are not allowed to access this library. */
45 /** The library is currently out of commission. */
50 * Return a name for this library (the UI may display this).
54 * @return the name, or an empty {@link String} if none
56 public String
getLibraryName() {
63 * @return the current status
65 public Status
getStatus() {
70 * Retrieve the main {@link File} corresponding to the given {@link Story},
71 * which can be passed to an external reader or instance.
73 * Do <b>NOT</b> alter this file.
76 * the Library UID of the story
78 * the optional {@link Progress}
80 * @return the corresponding {@link Story}
82 public abstract File
getFile(String luid
, Progress pg
);
85 * Return the cover image associated to this story.
88 * the Library UID of the story
90 * @return the cover image
92 public abstract Image
getCover(String luid
);
95 * Return the cover image associated to this source.
97 * By default, return the custom cover if any, and if not, return the cover
98 * of the first story with this source.
103 * @return the cover image or NULL
105 public Image
getSourceCover(String source
) {
106 Image custom
= getCustomSourceCover(source
);
107 if (custom
!= null) {
111 List
<MetaData
> metas
= getListBySource(source
);
112 if (metas
.size() > 0) {
113 return getCover(metas
.get(0).getLuid());
120 * Return the custom cover image associated to this source.
122 * By default, return NULL.
125 * the source to look for
127 * @return the custom cover or NULL if none
129 public Image
getCustomSourceCover(@SuppressWarnings("unused") String source
) {
134 * Fix the source cover to the given story cover.
137 * the source to change
141 public abstract void setSourceCover(String source
, String luid
);
144 * Return the list of stories (represented by their {@link MetaData}, which
145 * <b>MAY</b> not have the cover included).
148 * the optional {@link Progress}
150 * @return the list (can be empty but not NULL)
152 protected abstract List
<MetaData
> getMetas(Progress pg
);
155 * Invalidate the {@link Story} cache (when the content should be re-read
156 * because it was changed).
158 protected void deleteInfo() {
163 * Invalidate the {@link Story} cache (when the content is removed).
165 * All the cache can be deleted if NULL is passed as meta.
168 * the LUID of the {@link Story} to clear from the cache, or NULL
171 protected abstract void deleteInfo(String luid
);
174 * Invalidate the {@link Story} cache (when the content has changed, but we
175 * already have it) with the new given meta.
178 * the {@link Story} to clear from the cache
180 protected abstract void updateInfo(MetaData meta
);
183 * Return the next LUID that can be used.
185 * @return the next luid
187 protected abstract int getNextId();
190 * Delete the target {@link Story}.
193 * the LUID of the {@link Story}
195 * @throws IOException
196 * in case of I/O error or if the {@link Story} wa not found
198 protected abstract void doDelete(String luid
) throws IOException
;
201 * Actually save the story to the back-end.
204 * the {@link Story} to save
206 * the optional {@link Progress}
208 * @return the saved {@link Story} (which may have changed, especially
209 * regarding the {@link MetaData})
211 * @throws IOException
212 * in case of I/O error
214 protected abstract Story
doSave(Story story
, Progress pg
)
218 * Refresh the {@link BasicLibrary}, that is, make sure all metas are
222 * the optional progress reporter
224 public void refresh(Progress pg
) {
229 * List all the known types (sources) of stories.
231 * @return the sources
233 public synchronized List
<String
> getSources() {
234 List
<String
> list
= new ArrayList
<String
>();
235 for (MetaData meta
: getMetas(null)) {
236 String storySource
= meta
.getSource();
237 if (!list
.contains(storySource
)) {
238 list
.add(storySource
);
242 Collections
.sort(list
);
247 * List all the known authors of stories.
249 * @return the authors
251 public synchronized List
<String
> getAuthors() {
252 List
<String
> list
= new ArrayList
<String
>();
253 for (MetaData meta
: getMetas(null)) {
254 String storyAuthor
= meta
.getAuthor();
255 if (!list
.contains(storyAuthor
)) {
256 list
.add(storyAuthor
);
260 Collections
.sort(list
);
265 * List all the stories in the {@link BasicLibrary}.
267 * Cover images not included.
269 * @return the stories
271 public synchronized List
<MetaData
> getList() {
272 return getMetas(null);
276 * List all the stories of the given source type in the {@link BasicLibrary}
277 * , or all the stories if NULL is passed as a type.
279 * Cover images not included.
282 * the type of story to retrieve, or NULL for all
284 * @return the stories
286 public synchronized List
<MetaData
> getListBySource(String type
) {
287 List
<MetaData
> list
= new ArrayList
<MetaData
>();
288 for (MetaData meta
: getMetas(null)) {
289 String storyType
= meta
.getSource();
290 if (type
== null || type
.equalsIgnoreCase(storyType
)) {
295 Collections
.sort(list
);
300 * List all the stories of the given author in the {@link BasicLibrary}, or
301 * all the stories if NULL is passed as an author.
303 * Cover images not included.
306 * the author of the stories to retrieve, or NULL for all
308 * @return the stories
310 public synchronized List
<MetaData
> getListByAuthor(String author
) {
311 List
<MetaData
> list
= new ArrayList
<MetaData
>();
312 for (MetaData meta
: getMetas(null)) {
313 String storyAuthor
= meta
.getAuthor();
314 if (author
== null || author
.equalsIgnoreCase(storyAuthor
)) {
319 Collections
.sort(list
);
324 * Retrieve a {@link MetaData} corresponding to the given {@link Story},
325 * cover image <b>MAY</b> not be included.
328 * the Library UID of the story
330 * @return the corresponding {@link Story}
332 public synchronized MetaData
getInfo(String luid
) {
334 for (MetaData meta
: getMetas(null)) {
335 if (luid
.equals(meta
.getLuid())) {
345 * Retrieve a specific {@link Story}.
348 * the Library UID of the story
350 * the optional progress reporter
352 * @return the corresponding {@link Story} or NULL if not found
354 public synchronized Story
getStory(String luid
, Progress pg
) {
359 Progress pgGet
= new Progress();
360 Progress pgProcess
= new Progress();
363 pg
.addProgress(pgGet
, 1);
364 pg
.addProgress(pgProcess
, 1);
367 for (MetaData meta
: getMetas(null)) {
368 if (meta
.getLuid().equals(luid
)) {
369 File file
= getFile(luid
, pgGet
);
372 SupportType type
= SupportType
.valueOfAllOkUC(meta
374 URL url
= file
.toURI().toURL();
376 story
= BasicSupport
.getSupport(type
, url
) //
379 // Because we do not want to clear the meta cache:
380 meta
.setCover(story
.getMeta().getCover());
381 meta
.setResume(story
.getMeta().getResume());
385 throw new IOException("Unknown type: " + meta
.getType());
387 } catch (IOException e
) {
388 // We should not have not-supported files in the
390 Instance
.getTraceHandler().error(
391 new IOException("Cannot load file from library: "
406 * Import the {@link Story} at the given {@link URL} into the
407 * {@link BasicLibrary}.
410 * the {@link URL} to import
412 * the optional progress reporter
414 * @return the imported {@link Story}
416 * @throws UnknownHostException
417 * if the host is not supported
418 * @throws IOException
419 * in case of I/O error
421 public Story
imprt(URL url
, Progress pg
) throws IOException
{
425 pg
.setMinMax(0, 1000);
426 Progress pgProcess
= new Progress();
427 Progress pgSave
= new Progress();
428 pg
.addProgress(pgProcess
, 800);
429 pg
.addProgress(pgSave
, 200);
431 BasicSupport support
= BasicSupport
.getSupport(url
);
432 if (support
== null) {
433 throw new UnknownHostException("" + url
);
436 Story story
= save(support
.process(pgProcess
), pgSave
);
443 * Import the story from one library to another, and keep the same LUID.
446 * the other library to import from
450 * the optional progress reporter
452 * @throws IOException
453 * in case of I/O error
455 public void imprt(BasicLibrary other
, String luid
, Progress pg
)
457 Progress pgGetStory
= new Progress();
458 Progress pgSave
= new Progress();
464 pg
.addProgress(pgGetStory
, 1);
465 pg
.addProgress(pgSave
, 1);
467 Story story
= other
.getStory(luid
, pgGetStory
);
469 story
= this.save(story
, luid
, pgSave
);
473 throw new IOException("Cannot find story in Library: " + luid
);
478 * Export the {@link Story} to the given target in the given format.
481 * the {@link Story} ID
483 * the {@link OutputType} to transform it to
485 * the target to save to
487 * the optional progress reporter
489 * @return the saved resource (the main saved {@link File})
491 * @throws IOException
492 * in case of I/O error
494 public File
export(String luid
, OutputType type
, String target
, Progress pg
)
496 Progress pgGetStory
= new Progress();
497 Progress pgOut
= new Progress();
500 pg
.addProgress(pgGetStory
, 1);
501 pg
.addProgress(pgOut
, 1);
504 BasicOutput out
= BasicOutput
.getOutput(type
, false, false);
506 throw new IOException("Output type not supported: " + type
);
509 Story story
= getStory(luid
, pgGetStory
);
511 throw new IOException("Cannot find story to export: " + luid
);
514 return out
.process(story
, target
, pgOut
);
518 * Save a {@link Story} to the {@link BasicLibrary}.
521 * the {@link Story} to save
523 * the optional progress reporter
525 * @return the same {@link Story}, whose LUID may have changed
527 * @throws IOException
528 * in case of I/O error
530 public Story
save(Story story
, Progress pg
) throws IOException
{
531 return save(story
, null, pg
);
535 * Save a {@link Story} to the {@link BasicLibrary} -- the LUID <b>must</b>
536 * be correct, or NULL to get the next free one.
538 * Will override any previous {@link Story} with the same LUID.
541 * the {@link Story} to save
543 * the <b>correct</b> LUID or NULL to get the next free one
545 * the optional progress reporter
547 * @return the same {@link Story}, whose LUID may have changed
549 * @throws IOException
550 * in case of I/O error
552 public synchronized Story
save(Story story
, String luid
, Progress pg
)
555 Instance
.getTraceHandler().trace(
556 this.getClass().getSimpleName() + ": saving story " + luid
);
558 // Do not change the original metadata, but change the original story
559 MetaData meta
= story
.getMeta().clone();
562 if (luid
== null || luid
.isEmpty()) {
563 meta
.setLuid(String
.format("%03d", getNextId()));
568 if (luid
!= null && getInfo(luid
) != null) {
572 story
= doSave(story
, pg
);
574 updateInfo(story
.getMeta());
576 Instance
.getTraceHandler().trace(
577 this.getClass().getSimpleName() + ": story saved (" + luid
584 * Delete the given {@link Story} from this {@link BasicLibrary}.
587 * the LUID of the target {@link Story}
589 * @throws IOException
590 * in case of I/O error
592 public synchronized void delete(String luid
) throws IOException
{
593 Instance
.getTraceHandler().trace(
594 this.getClass().getSimpleName() + ": deleting story " + luid
);
599 Instance
.getTraceHandler().trace(
600 this.getClass().getSimpleName() + ": story deleted (" + luid
605 * Change the type (source) of the given {@link Story}.
608 * the {@link Story} LUID
612 * the optional progress reporter
614 * @throws IOException
615 * in case of I/O error or if the {@link Story} was not found
617 public synchronized void changeSource(String luid
, String newSource
,
618 Progress pg
) throws IOException
{
619 MetaData meta
= getInfo(luid
);
621 throw new IOException("Story not found: " + luid
);
624 meta
.setSource(newSource
);
629 * Save back the current state of the {@link MetaData} (LUID <b>MUST NOT</b>
630 * change) for this {@link Story}.
632 * By default, delete the old {@link Story} then recreate a new
635 * Note that this behaviour can lead to data loss.
638 * the new {@link MetaData} (LUID <b>MUST NOT</b> change)
640 * the optional {@link Progress}
642 * @throws IOException
643 * in case of I/O error or if the {@link Story} was not found
645 protected synchronized void saveMeta(MetaData meta
, Progress pg
)
651 Progress pgGet
= new Progress();
652 Progress pgSet
= new Progress();
653 pg
.addProgress(pgGet
, 50);
654 pg
.addProgress(pgSet
, 50);
656 Story story
= getStory(meta
.getLuid(), pgGet
);
658 throw new IOException("Story not found: " + meta
.getLuid());
661 delete(meta
.getLuid());
664 save(story
, meta
.getLuid(), pgSet
);