X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Flibrary%2FBasicLibrary.java;h=8b72f19b3a92ad1c1d7ada09d915440596330ee2;hp=29a3cf97efc162fb86f7035585e853fbbc25ce0a;hb=acbec0d232186ddf5431b696ee74a791ae5e828f;hpb=ed2fd793b8768d7865afb2176375a6459e2408d2 diff --git a/src/be/nikiroo/fanfix/library/BasicLibrary.java b/src/be/nikiroo/fanfix/library/BasicLibrary.java index 29a3cf9..8b72f19 100644 --- a/src/be/nikiroo/fanfix/library/BasicLibrary.java +++ b/src/be/nikiroo/fanfix/library/BasicLibrary.java @@ -4,9 +4,9 @@ import java.io.File; import java.io.IOException; import java.net.URL; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.data.MetaData; @@ -17,6 +17,7 @@ import be.nikiroo.fanfix.supported.BasicSupport; import be.nikiroo.fanfix.supported.SupportType; import be.nikiroo.utils.Image; import be.nikiroo.utils.Progress; +import be.nikiroo.utils.StringUtils; /** * Manage a library of Stories: import, export, list, modify. @@ -36,14 +37,36 @@ abstract public class BasicLibrary { * @author niki */ public enum Status { - /** The library is ready. */ - READY, - /** The library is invalid (not correctly set up). */ - INVALID, + /** The library is ready and r/w. */ + READ_WRITE, + /** The library is ready, but read-only. */ + READ_ONLY, /** You are not allowed to access this library. */ - UNAUTORIZED, - /** The library is currently out of commission. */ - UNAVAILABLE, + UNAUTHORIZED, + /** The library is invalid, and will never work as is. */ + INVALID, + /** The library is currently out of commission, but may work later. */ + UNAVAILABLE; + + /** + * The library is available (you can query it). + *

+ * It does not specify if it is read-only or not. + * + * @return TRUE if it is + */ + public boolean isReady() { + return (this == READ_WRITE || this == READ_ONLY); + } + + /** + * This library can be modified (= you are allowed to modify it). + * + * @return TRUE if it is + */ + public boolean isWritable() { + return (this == READ_WRITE); + } } /** @@ -63,7 +86,7 @@ abstract public class BasicLibrary { * @return the current status */ public Status getStatus() { - return Status.READY; + return Status.READ_WRITE; } /** @@ -73,13 +96,16 @@ abstract public class BasicLibrary { * Do NOT alter this file. * * @param luid - * the Library UID of the story + * the Library UID of the story, can be NULL * @param pg * the optional {@link Progress} * * @return the corresponding {@link Story} + * + * @throws IOException + * in case of IOException */ - public abstract File getFile(String luid, Progress pg); + public abstract File getFile(String luid, Progress pg) throws IOException; /** * Return the cover image associated to this story. @@ -88,8 +114,30 @@ abstract public class BasicLibrary { * the Library UID of the story * * @return the cover image + * + * @throws IOException + * in case of IOException */ - public abstract Image getCover(String luid); + public abstract Image getCover(String luid) throws IOException; + + /** + * Retrieve the list of {@link MetaData} known by this {@link BasicLibrary} + * in a easy-to-filter version. + * + * @param pg + * the optional {@link Progress} + * @return the list of {@link MetaData} as a {@link MetaResultList} you can + * query + * @throws IOException + * in case of I/O eror + */ + public MetaResultList getList(Progress pg) throws IOException { + // TODO: ensure it is the main used interface + + return new MetaResultList(getMetas(pg)); + } + + // TODO: make something for (normal and custom) non-story covers /** * Return the cover image associated to this source. @@ -101,14 +149,45 @@ abstract public class BasicLibrary { * the source * * @return the cover image or NULL + * + * @throws IOException + * in case of IOException */ - public Image getSourceCover(String source) { + public Image getSourceCover(String source) throws IOException { Image custom = getCustomSourceCover(source); if (custom != null) { return custom; } - List metas = getListBySource(source); + List metas = getList().filter(source, null, null); + if (metas.size() > 0) { + return getCover(metas.get(0).getLuid()); + } + + return null; + } + + /** + * Return the cover image associated to this author. + *

+ * By default, return the custom cover if any, and if not, return the cover + * of the first story with this author. + * + * @param author + * the author + * + * @return the cover image or NULL + * + * @throws IOException + * in case of IOException + */ + public Image getAuthorCover(String author) throws IOException { + Image custom = getCustomAuthorCover(author); + if (custom != null) { + return custom; + } + + List metas = getList().filter(null, author, null); if (metas.size() > 0) { return getCover(metas.get(0).getLuid()); } @@ -125,38 +204,83 @@ abstract public class BasicLibrary { * the source to look for * * @return the custom cover or NULL if none + * + * @throws IOException + * in case of IOException + */ + @SuppressWarnings("unused") + public Image getCustomSourceCover(String source) throws IOException { + return null; + } + + /** + * Return the custom cover image associated to this author. + *

+ * By default, return NULL. + * + * @param author + * the author to look for + * + * @return the custom cover or NULL if none + * + * @throws IOException + * in case of IOException */ - public Image getCustomSourceCover(@SuppressWarnings("unused") String source) { + @SuppressWarnings("unused") + public Image getCustomAuthorCover(String author) throws IOException { return null; } /** - * Fix the source cover to the given story cover. + * Set the source cover to the given story cover. * * @param source * the source to change * @param luid * the story LUID + * + * @throws IOException + * in case of IOException */ - public abstract void setSourceCover(String source, String luid); + public abstract void setSourceCover(String source, String luid) + throws IOException; + + /** + * Set the author cover to the given story cover. + * + * @param author + * the author to change + * @param luid + * the story LUID + * + * @throws IOException + * in case of IOException + */ + public abstract void setAuthorCover(String author, String luid) + throws IOException; /** * Return the list of stories (represented by their {@link MetaData}, which * MAY not have the cover included). + *

+ * The returned list MUST be a copy, not the original one. * * @param pg * the optional {@link Progress} * * @return the list (can be empty but not NULL) + * + * @throws IOException + * in case of IOException */ - protected abstract List getMetas(Progress pg); + protected abstract List getMetas(Progress pg) throws IOException; /** * Invalidate the {@link Story} cache (when the content should be re-read * because it was changed). */ - protected void deleteInfo() { - deleteInfo(null); + protected void invalidateInfo() { + invalidateInfo(null); } /** @@ -168,7 +292,7 @@ abstract public class BasicLibrary { * the LUID of the {@link Story} to clear from the cache, or NULL * for all stories */ - protected abstract void deleteInfo(String luid); + protected abstract void invalidateInfo(String luid); /** * Invalidate the {@link Story} cache (when the content has changed, but we @@ -176,15 +300,18 @@ abstract public class BasicLibrary { * * @param meta * the {@link Story} to clear from the cache + * + * @throws IOException + * in case of IOException */ - protected abstract void updateInfo(MetaData meta); + protected abstract void updateInfo(MetaData meta) throws IOException; /** * Return the next LUID that can be used. * * @return the next luid */ - protected abstract int getNextId(); + protected abstract String getNextId(); /** * Delete the target {@link Story}. @@ -222,102 +349,104 @@ abstract public class BasicLibrary { * the optional progress reporter */ public void refresh(Progress pg) { - getMetas(pg); + try { + getMetas(pg); + } catch (IOException e) { + // We will let it fail later + } } /** - * List all the known types (sources) of stories. + * Check if the {@link Story} denoted by this Library UID is present in the + * cache (if we have no cache, we default to true). * - * @return the sources + * @param luid + * the Library UID + * + * @return TRUE if it is */ - public synchronized List getSources() { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storySource = meta.getSource(); - if (!list.contains(storySource)) { - list.add(storySource); - } - } - - Collections.sort(list); - return list; + public boolean isCached(@SuppressWarnings("unused") String luid) { + // By default, everything is cached + return true; } /** - * List all the known authors of stories. + * Clear the {@link Story} from the cache, if needed. + *

+ * The next time we try to retrieve the {@link Story}, it may be required to + * cache it again. + * + * @param luid + * the story to clear * - * @return the authors + * @throws IOException + * in case of I/O error */ - public synchronized List getAuthors() { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storyAuthor = meta.getAuthor(); - if (!list.contains(storyAuthor)) { - list.add(storyAuthor); - } - } + @SuppressWarnings("unused") + public void clearFromCache(String luid) throws IOException { + // By default, this is a noop. + } - Collections.sort(list); - return list; + /** + * @return the same as getList() + * @throws IOException + * in case of I/O error + * @deprecated please use {@link BasicLibrary#getList()} and + * {@link MetaResultList#getSources()} instead. + */ + @Deprecated + public List getSources() throws IOException { + return getList().getSources(); } /** - * List all the stories in the {@link BasicLibrary}. - *

- * Cover images not included. - * - * @return the stories + * @return the same as getList() + * @throws IOException + * in case of I/O error + * @deprecated please use {@link BasicLibrary#getList()} and + * {@link MetaResultList#getSourcesGrouped()} instead. */ - public synchronized List getList() { - return getMetas(null); + @Deprecated + public Map> getSourcesGrouped() throws IOException { + return getList().getSourcesGrouped(); } /** - * List all the stories of the given source type in the {@link BasicLibrary} - * , or all the stories if NULL is passed as a type. - *

- * Cover images not included. - * - * @param type - * the type of story to retrieve, or NULL for all - * - * @return the stories + * @return the same as getList() + * @throws IOException + * in case of I/O error + * @deprecated please use {@link BasicLibrary#getList()} and + * {@link MetaResultList#getAuthors()} instead. */ - public synchronized List getListBySource(String type) { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storyType = meta.getSource(); - if (type == null || type.equalsIgnoreCase(storyType)) { - list.add(meta); - } - } + @Deprecated + public List getAuthors() throws IOException { + return getList().getAuthors(); + } - Collections.sort(list); - return list; + /** + * @return the same as getList() + * @throws IOException + * in case of I/O error + * @deprecated please use {@link BasicLibrary#getList()} and + * {@link MetaResultList#getAuthorsGrouped()} instead. + */ + @Deprecated + public Map> getAuthorsGrouped() throws IOException { + return getList().getAuthorsGrouped(); } /** - * List all the stories of the given author in the {@link BasicLibrary}, or - * all the stories if NULL is passed as an author. + * List all the stories in the {@link BasicLibrary}. *

- * Cover images not included. - * - * @param author - * the author of the stories to retrieve, or NULL for all + * Cover images MAYBE not included. * * @return the stories + * + * @throws IOException + * in case of IOException */ - public synchronized List getListByAuthor(String author) { - List list = new ArrayList(); - for (MetaData meta : getMetas(null)) { - String storyAuthor = meta.getAuthor(); - if (author == null || author.equalsIgnoreCase(storyAuthor)) { - list.add(meta); - } - } - - Collections.sort(list); - return list; + public MetaResultList getList() throws IOException { + return getList(null); } /** @@ -325,11 +454,14 @@ abstract public class BasicLibrary { * cover image MAY not be included. * * @param luid - * the Library UID of the story + * the Library UID of the story, can be NULL * - * @return the corresponding {@link Story} + * @return the corresponding {@link Story} or NULL if not found + * + * @throws IOException + * in case of IOException */ - public synchronized MetaData getInfo(String luid) { + public MetaData getInfo(String luid) throws IOException { if (luid != null) { for (MetaData meta : getMetas(null)) { if (luid.equals(meta.getLuid())) { @@ -350,8 +482,53 @@ abstract public class BasicLibrary { * the optional progress reporter * * @return the corresponding {@link Story} or NULL if not found + * + * @throws IOException + * in case of IOException + */ + public Story getStory(String luid, Progress pg) throws IOException { + Progress pgMetas = new Progress(); + Progress pgStory = new Progress(); + if (pg != null) { + pg.setMinMax(0, 100); + pg.addProgress(pgMetas, 10); + pg.addProgress(pgStory, 90); + } + + MetaData meta = null; + for (MetaData oneMeta : getMetas(pgMetas)) { + if (oneMeta.getLuid().equals(luid)) { + meta = oneMeta; + break; + } + } + + pgMetas.done(); + + Story story = getStory(luid, meta, pgStory); + pgStory.done(); + + return story; + } + + /** + * Retrieve a specific {@link Story}. + * + * @param luid + * the LUID of the story + * @param meta + * the meta of the story + * @param pg + * the optional progress reporter + * + * @return the corresponding {@link Story} or NULL if not found + * + * @throws IOException + * in case of IOException */ - public synchronized Story getStory(String luid, Progress pg) { + public synchronized Story getStory(String luid, MetaData meta, Progress pg) + throws IOException { + if (pg == null) { pg = new Progress(); } @@ -364,39 +541,38 @@ abstract public class BasicLibrary { pg.addProgress(pgProcess, 1); Story story = null; - for (MetaData meta : getMetas(null)) { - if (meta.getLuid().equals(luid)) { - File file = getFile(luid, pgGet); - pgGet.done(); - try { - SupportType type = SupportType.valueOfAllOkUC(meta - .getType()); - URL url = file.toURI().toURL(); - if (type != null) { - story = BasicSupport.getSupport(type, url) // - .process(pgProcess); - - // Because we do not want to clear the meta cache: - meta.setCover(story.getMeta().getCover()); - meta.setResume(story.getMeta().getResume()); - story.setMeta(meta); - // - } else { - throw new IOException("Unknown type: " + meta.getType()); - } - } catch (IOException e) { - // We should not have not-supported files in the - // library - Instance.getTraceHandler().error( - new IOException("Cannot load file from library: " - + file, e)); - } finally { - pgProcess.done(); - pg.done(); + File file = null; + + if (luid != null && meta != null) { + file = getFile(luid, pgGet); + } + + pgGet.done(); + try { + if (file != null) { + SupportType type = SupportType.valueOfAllOkUC(meta.getType()); + if (type == null) { + throw new IOException("Unknown type: " + meta.getType()); } - break; + URL url = file.toURI().toURL(); + story = BasicSupport.getSupport(type, url) // + .process(pgProcess); + + // Because we do not want to clear the meta cache: + meta.setCover(story.getMeta().getCover()); + meta.setResume(story.getMeta().getResume()); + story.setMeta(meta); } + } catch (IOException e) { + // We should not have not-supported files in the library + Instance.getInstance().getTraceHandler() + .error(new IOException(String.format( + "Cannot load file of type '%s' from library: %s", + meta.getType(), file), e)); + } finally { + pgProcess.done(); + pg.done(); } return story; @@ -411,14 +587,36 @@ abstract public class BasicLibrary { * @param pg * the optional progress reporter * - * @return the imported {@link Story} + * @return the imported Story {@link MetaData} * * @throws UnknownHostException * if the host is not supported * @throws IOException * in case of I/O error */ - public Story imprt(URL url, Progress pg) throws IOException { + public MetaData imprt(URL url, Progress pg) throws IOException { + return imprt(url, null, pg); + } + + /** + * Import the {@link Story} at the given {@link URL} into the + * {@link BasicLibrary}. + * + * @param url + * the {@link URL} to import + * @param luid + * the LUID to use + * @param pg + * the optional progress reporter + * + * @return the imported Story {@link MetaData} + * + * @throws UnknownHostException + * if the host is not supported + * @throws IOException + * in case of I/O error + */ + MetaData imprt(URL url, String luid, Progress pg) throws IOException { if (pg == null) pg = new Progress(); @@ -433,10 +631,10 @@ abstract public class BasicLibrary { throw new UnknownHostException("" + url); } - Story story = save(support.process(pgProcess), pgSave); + Story story = save(support.process(pgProcess), luid, pgSave); pg.done(); - return story; + return story.getMeta(); } /** @@ -551,16 +749,21 @@ abstract public class BasicLibrary { */ public synchronized Story save(Story story, String luid, Progress pg) throws IOException { + if (pg == null) { + pg = new Progress(); + } - Instance.getTraceHandler().trace( + Instance.getInstance().getTraceHandler().trace( this.getClass().getSimpleName() + ": saving story " + luid); // Do not change the original metadata, but change the original story MetaData meta = story.getMeta().clone(); story.setMeta(meta); + pg.setName("Saving story"); + if (luid == null || luid.isEmpty()) { - meta.setLuid(String.format("%03d", getNextId())); + meta.setLuid(getNextId()); } else { meta.setLuid(luid); } @@ -573,10 +776,12 @@ abstract public class BasicLibrary { updateInfo(story.getMeta()); - Instance.getTraceHandler().trace( - this.getClass().getSimpleName() + ": story saved (" + luid - + ")"); + Instance.getInstance().getTraceHandler() + .trace(this.getClass().getSimpleName() + ": story saved (" + + luid + ")"); + pg.setName(meta.getTitle()); + pg.done(); return story; } @@ -590,15 +795,15 @@ abstract public class BasicLibrary { * in case of I/O error */ public synchronized void delete(String luid) throws IOException { - Instance.getTraceHandler().trace( + Instance.getInstance().getTraceHandler().trace( this.getClass().getSimpleName() + ": deleting story " + luid); doDelete(luid); - deleteInfo(luid); + invalidateInfo(luid); - Instance.getTraceHandler().trace( - this.getClass().getSimpleName() + ": story deleted (" + luid - + ")"); + Instance.getInstance().getTraceHandler() + .trace(this.getClass().getSimpleName() + ": story deleted (" + + luid + ")"); } /** @@ -621,7 +826,83 @@ abstract public class BasicLibrary { throw new IOException("Story not found: " + luid); } + changeSTA(luid, newSource, meta.getTitle(), meta.getAuthor(), pg); + } + + /** + * Change the title (name) of the given {@link Story}. + * + * @param luid + * the {@link Story} LUID + * @param newTitle + * the new title + * @param pg + * the optional progress reporter + * + * @throws IOException + * in case of I/O error or if the {@link Story} was not found + */ + public synchronized void changeTitle(String luid, String newTitle, + Progress pg) throws IOException { + MetaData meta = getInfo(luid); + if (meta == null) { + throw new IOException("Story not found: " + luid); + } + + changeSTA(luid, meta.getSource(), newTitle, meta.getAuthor(), pg); + } + + /** + * Change the author of the given {@link Story}. + * + * @param luid + * the {@link Story} LUID + * @param newAuthor + * the new author + * @param pg + * the optional progress reporter + * + * @throws IOException + * in case of I/O error or if the {@link Story} was not found + */ + public synchronized void changeAuthor(String luid, String newAuthor, + Progress pg) throws IOException { + MetaData meta = getInfo(luid); + if (meta == null) { + throw new IOException("Story not found: " + luid); + } + + changeSTA(luid, meta.getSource(), meta.getTitle(), newAuthor, pg); + } + + /** + * Change the Source, Title and Author of the {@link Story} in one single + * go. + * + * @param luid + * the {@link Story} LUID + * @param newSource + * the new source + * @param newTitle + * the new title + * @param newAuthor + * the new author + * @param pg + * the optional progress reporter + * + * @throws IOException + * in case of I/O error or if the {@link Story} was not found + */ + protected synchronized void changeSTA(String luid, String newSource, + String newTitle, String newAuthor, Progress pg) throws IOException { + MetaData meta = getInfo(luid); + if (meta == null) { + throw new IOException("Story not found: " + luid); + } + meta.setSource(newSource); + meta.setTitle(newTitle); + meta.setAuthor(newAuthor); saveMeta(meta, pg); } @@ -632,7 +913,7 @@ abstract public class BasicLibrary { * By default, delete the old {@link Story} then recreate a new * {@link Story}. *

- * Note that this behaviour can lead to data loss. + * Note that this behaviour can lead to data loss in case of problems! * * @param meta * the new {@link MetaData} (LUID MUST NOT change) @@ -658,11 +939,56 @@ abstract public class BasicLibrary { throw new IOException("Story not found: " + meta.getLuid()); } + // TODO: this is not safe! delete(meta.getLuid()); - story.setMeta(meta); save(story, meta.getLuid(), pgSet); pg.done(); } + + /** + * Describe a {@link Story} from its {@link MetaData} and return a list of + * title/value that represent this {@link Story}. + * + * @param meta + * the {@link MetaData} to represent + * + * @return the information, translated and sorted + */ + static public Map getMetaDesc(MetaData meta) { + Map metaDesc = new LinkedHashMap(); + + // TODO: i18n + + StringBuilder tags = new StringBuilder(); + for (String tag : meta.getTags()) { + if (tags.length() > 0) { + tags.append(", "); + } + tags.append(tag); + } + + // TODO: i18n + metaDesc.put("Author", meta.getAuthor()); + metaDesc.put("Published on", meta.getPublisher()); + metaDesc.put("Publication date", meta.getDate()); + metaDesc.put("Creation date", meta.getCreationDate()); + String count = ""; + if (meta.getWords() > 0) { + count = StringUtils.formatNumber(meta.getWords()); + } + if (meta.isImageDocument()) { + metaDesc.put("Number of images", count); + } else { + metaDesc.put("Number of words", count); + } + metaDesc.put("Source", meta.getSource()); + metaDesc.put("Subject", meta.getSubject()); + metaDesc.put("Language", meta.getLang()); + metaDesc.put("Tags", tags.toString()); + metaDesc.put("URL", meta.getUrl()); + + return metaDesc; + } }