Instance: use getInstance()
[nikiroo-utils.git] / src / be / nikiroo / fanfix / library / BasicLibrary.java
index b3b49fdbce50ec5aa6b4877af9c42b2a949e39df..c558384d380526819f36a8dac9553b89d33517ca 100644 (file)
@@ -4,12 +4,10 @@ import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.net.UnknownHostException;
-import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.TreeMap;
 
 import be.nikiroo.fanfix.Instance;
@@ -41,14 +39,36 @@ abstract public class BasicLibrary {
         * @author niki
         */
        public enum Status {
-               /** The library is ready. */
-               READY,
+               /** The library is ready and r/w. */
+               READ_WRITE,
+               /** The library is ready, but read-only. */
+               READ_ONLY,
                /** The library is invalid (not correctly set up). */
                INVALID,
                /** You are not allowed to access this library. */
-               UNAUTORIZED,
+               UNAUTHORIZED,
                /** The library is currently out of commission. */
-               UNAVAILABLE,
+               UNAVAILABLE;
+
+               /**
+                * The library is available (you can query it).
+                * <p>
+                * It does <b>not</b> 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);
+               }
        }
 
        /**
@@ -68,7 +88,7 @@ abstract public class BasicLibrary {
         * @return the current status
         */
        public Status getStatus() {
-               return Status.READY;
+               return Status.READ_WRITE;
        }
 
        /**
@@ -83,8 +103,11 @@ abstract public class BasicLibrary {
         *            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.
@@ -93,8 +116,11 @@ 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;
 
        /**
         * Return the cover image associated to this source.
@@ -106,8 +132,11 @@ 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;
@@ -121,6 +150,34 @@ abstract public class BasicLibrary {
                return null;
        }
 
+       /**
+        * Return the cover image associated to this author.
+        * <p>
+        * 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<MetaData> metas = getListByAuthor(author);
+               if (metas.size() > 0) {
+                       return getCover(metas.get(0).getLuid());
+               }
+
+               return null;
+       }
+
        /**
         * Return the custom cover image associated to this source.
         * <p>
@@ -130,20 +187,60 @@ 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.
+        * <p>
+        * 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)
+                       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 setSourceCover(String source, String luid);
+       public abstract void setAuthorCover(String author, String luid)
+                       throws IOException;
 
        /**
         * Return the list of stories (represented by their {@link MetaData}, which
@@ -153,8 +250,11 @@ abstract public class BasicLibrary {
         *            the optional {@link Progress}
         * 
         * @return the list (can be empty but not NULL)
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       protected abstract List<MetaData> getMetas(Progress pg);
+       protected abstract List<MetaData> getMetas(Progress pg) throws IOException;
 
        /**
         * Invalidate the {@link Story} cache (when the content should be re-read
@@ -181,8 +281,11 @@ 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.
@@ -227,15 +330,22 @@ 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.
         * 
         * @return the sources
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized List<String> getSources() {
+       public synchronized List<String> getSources() throws IOException {
                List<String> list = new ArrayList<String>();
                for (MetaData meta : getMetas(null)) {
                        String storySource = meta.getSource();
@@ -261,8 +371,12 @@ abstract public class BasicLibrary {
         * </ul>
         * 
         * @return the grouped list
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized Map<String, List<String>> getSourcesGrouped() {
+       public synchronized Map<String, List<String>> getSourcesGrouped()
+                       throws IOException {
                Map<String, List<String>> map = new TreeMap<String, List<String>>();
                for (String source : getSources()) {
                        String name;
@@ -293,8 +407,11 @@ abstract public class BasicLibrary {
         * List all the known authors of stories.
         * 
         * @return the authors
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized List<String> getAuthors() {
+       public synchronized List<String> getAuthors() throws IOException {
                List<String> list = new ArrayList<String>();
                for (MetaData meta : getMetas(null)) {
                        String storyAuthor = meta.getAuthor();
@@ -325,49 +442,64 @@ abstract public class BasicLibrary {
         * <tt>0-9</tt>, which may only be present or not).
         * 
         * @return the authors' names, grouped by letter(s)
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public List<Entry<String, List<String>>> getAuthorsGrouped() {
+       public Map<String, List<String>> getAuthorsGrouped() throws IOException {
                int MAX = 20;
 
-               List<Entry<String, List<String>>> groups = new ArrayList<Entry<String, List<String>>>();
+               Map<String, List<String>> groups = new TreeMap<String, List<String>>();
                List<String> authors = getAuthors();
 
+               // If all authors fit the max, just report them as is
                if (authors.size() <= MAX) {
-                       groups.add(new SimpleEntry<String, List<String>>("", authors));
+                       groups.put("", authors);
                        return groups;
                }
 
-               groups.add(new SimpleEntry<String, List<String>>("*", getAuthorsGroup(
-                               authors, '*')));
-               groups.add(new SimpleEntry<String, List<String>>("0-9",
-                               getAuthorsGroup(authors, '0')));
-
+               // Create groups A to Z, which can be empty here
                for (char car = 'A'; car <= 'Z'; car++) {
-                       groups.add(new SimpleEntry<String, List<String>>(Character
-                                       .toString(car), getAuthorsGroup(authors, car)));
+                       groups.put(Character.toString(car), getAuthorsGroup(authors, car));
                }
 
-               // do NOT collapse * and [0-9] with the rest
-               for (int i = 2; i + 1 < groups.size(); i++) {
-                       Entry<String, List<String>> now = groups.get(i);
-                       Entry<String, List<String>> next = groups.get(i + 1);
-                       int currentTotal = now.getValue().size() + next.getValue().size();
+               // Collapse them
+               List<String> keys = new ArrayList<String>(groups.keySet());
+               for (int i = 0; i + 1 < keys.size(); i++) {
+                       String keyNow = keys.get(i);
+                       String keyNext = keys.get(i + 1);
+
+                       List<String> now = groups.get(keyNow);
+                       List<String> next = groups.get(keyNext);
+
+                       int currentTotal = now.size() + next.size();
                        if (currentTotal <= MAX) {
-                               String key = now.getKey().charAt(0) + "-"
-                                               + next.getKey().charAt(next.getKey().length() - 1);
+                               String key = keyNow.charAt(0) + "-"
+                                               + keyNext.charAt(keyNext.length() - 1);
+
                                List<String> all = new ArrayList<String>();
-                               all.addAll(now.getValue());
-                               all.addAll(next.getValue());
-                               groups.set(i, new SimpleEntry<String, List<String>>(key, all));
-                               groups.remove(i + 1);
-                               i--;
+                               all.addAll(now);
+                               all.addAll(next);
+
+                               groups.remove(keyNow);
+                               groups.remove(keyNext);
+                               groups.put(key, all);
+
+                               keys.set(i, key); // set the new key instead of key(i)
+                               keys.remove(i + 1); // remove the next, consumed key
+                               i--; // restart at key(i)
                        }
                }
 
-               for (int i = 0; i < groups.size(); i++) {
-                       if (groups.get(i).getValue().size() == 0) {
-                               groups.remove(i);
-                               i--;
+               // Add "special" groups
+               groups.put("*", getAuthorsGroup(authors, '*'));
+               groups.put("0-9", getAuthorsGroup(authors, '0'));
+
+               // Prune empty groups
+               keys = new ArrayList<String>(groups.keySet());
+               for (String key : keys) {
+                       if (groups.get(key).isEmpty()) {
+                               groups.remove(key);
                        }
                }
 
@@ -389,7 +521,8 @@ abstract public class BasicLibrary {
         * @param car
         *            the starting character, <tt>*</tt>, <tt>0</tt> or a capital
         *            letter
-        * @return the authors that fulfill the starting letter
+        * 
+        * @return the authors that fulfil the starting letter
         */
        private List<String> getAuthorsGroup(List<String> authors, char car) {
                List<String> accepted = new ArrayList<String>();
@@ -421,8 +554,11 @@ abstract public class BasicLibrary {
         * Cover images <b>MAYBE</b> not included.
         * 
         * @return the stories
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized List<MetaData> getList() {
+       public synchronized List<MetaData> getList() throws IOException {
                return getMetas(null);
        }
 
@@ -436,8 +572,12 @@ abstract public class BasicLibrary {
         *            the type of story to retrieve, or NULL for all
         * 
         * @return the stories
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized List<MetaData> getListBySource(String type) {
+       public synchronized List<MetaData> getListBySource(String type)
+                       throws IOException {
                List<MetaData> list = new ArrayList<MetaData>();
                for (MetaData meta : getMetas(null)) {
                        String storyType = meta.getSource();
@@ -460,8 +600,12 @@ abstract public class BasicLibrary {
         *            the author of the stories to retrieve, or NULL for all
         * 
         * @return the stories
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized List<MetaData> getListByAuthor(String author) {
+       public synchronized List<MetaData> getListByAuthor(String author)
+                       throws IOException {
                List<MetaData> list = new ArrayList<MetaData>();
                for (MetaData meta : getMetas(null)) {
                        String storyAuthor = meta.getAuthor();
@@ -482,8 +626,11 @@ abstract public class BasicLibrary {
         *            the Library UID of the story
         * 
         * @return the corresponding {@link Story}
+        * 
+        * @throws IOException
+        *             in case of IOException
         */
-       public synchronized MetaData getInfo(String luid) {
+       public synchronized MetaData getInfo(String luid) throws IOException {
                if (luid != null) {
                        for (MetaData meta : getMetas(null)) {
                                if (luid.equals(meta.getLuid())) {
@@ -504,8 +651,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 synchronized 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 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,
+                       @SuppressWarnings("javadoc") MetaData meta, Progress pg)
+                       throws IOException {
+
                if (pg == null) {
                        pg = new Progress();
                }
@@ -518,39 +710,31 @@ 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();
-                               }
-
-                               break;
+               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.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;
@@ -565,14 +749,14 @@ 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 {
                if (pg == null)
                        pg = new Progress();
 
@@ -590,7 +774,7 @@ abstract public class BasicLibrary {
                Story story = save(support.process(pgProcess), pgSave);
                pg.done();
 
-               return story;
+               return story.getMeta();
        }
 
        /**
@@ -706,8 +890,7 @@ abstract public class BasicLibrary {
        public synchronized Story save(Story story, String luid, Progress pg)
                        throws IOException {
 
-               Instance.getTraceHandler().trace(
-                               this.getClass().getSimpleName() + ": saving story " + luid);
+               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();
@@ -727,9 +910,8 @@ 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 + ")");
 
                return story;
        }
@@ -744,14 +926,13 @@ abstract public class BasicLibrary {
         *             in case of I/O error
         */
        public synchronized void delete(String luid) throws IOException {
-               Instance.getTraceHandler().trace(
-                               this.getClass().getSimpleName() + ": deleting story " + luid);
+               Instance.getInstance().getTraceHandler().trace(this.getClass().getSimpleName() + ": deleting story " + luid);
 
                doDelete(luid);
                invalidateInfo(luid);
 
-               Instance.getTraceHandler().trace(
-                               this.getClass().getSimpleName() + ": story deleted (" + luid
+               Instance.getInstance().getTraceHandler()
+                               .trace(this.getClass().getSimpleName() + ": story deleted (" + luid
                                                + ")");
        }
 
@@ -853,6 +1034,8 @@ abstract public class BasicLibrary {
                meta.setTitle(newTitle);
                meta.setAuthor(newAuthor);
                saveMeta(meta, pg);
+
+               invalidateInfo(luid);
        }
 
        /**