Dependency fix + Local/Remote Library support
authorNiki Roo <niki@nikiroo.be>
Sun, 2 Jul 2017 11:22:14 +0000 (13:22 +0200)
committerNiki Roo <niki@nikiroo.be>
Sun, 2 Jul 2017 11:22:14 +0000 (13:22 +0200)
- Fix nikiroo-utils (a bad file was used for version 1.6.1)
- Implement a new remote Library and rework the Library class
- Add unit tests for the Library

15 files changed:
libs/nikiroo-utils-1.6.1-sources.jar
src/be/nikiroo/fanfix/BasicLibrary.java [new file with mode: 0644]
src/be/nikiroo/fanfix/Instance.java
src/be/nikiroo/fanfix/Library.java [deleted file]
src/be/nikiroo/fanfix/LocalLibrary.java [new file with mode: 0644]
src/be/nikiroo/fanfix/Main.java
src/be/nikiroo/fanfix/RemoteLibrary.java
src/be/nikiroo/fanfix/RemoteLibraryServer.java
src/be/nikiroo/fanfix/package-info.java
src/be/nikiroo/fanfix/reader/BasicReader.java
src/be/nikiroo/fanfix/reader/LocalReader.java
src/be/nikiroo/fanfix/reader/LocalReaderFrame.java
src/be/nikiroo/fanfix/reader/TuiReader.java
src/be/nikiroo/fanfix/reader/TuiReaderStoryWindow.java
src/be/nikiroo/fanfix/test/LibraryTest.java

index f3b1578e33be4dce4635d0b11b11efd27c51ce98..66f67343245d71b1cfe99b4603ea6d60ccb162ae 100644 (file)
Binary files a/libs/nikiroo-utils-1.6.1-sources.jar and b/libs/nikiroo-utils-1.6.1-sources.jar differ
diff --git a/src/be/nikiroo/fanfix/BasicLibrary.java b/src/be/nikiroo/fanfix/BasicLibrary.java
new file mode 100644 (file)
index 0000000..75cc8a6
--- /dev/null
@@ -0,0 +1,488 @@
+package be.nikiroo.fanfix;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.output.BasicOutput;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
+import be.nikiroo.utils.Progress;
+
+/**
+ * Manage a library of Stories: import, export, list, modify.
+ * <p>
+ * Each {@link Story} object will be associated with a (local to the library)
+ * unique ID, the LUID, which will be used to identify the {@link Story}.
+ * <p>
+ * Most of the {@link BasicLibrary} functions work on a partial (cover
+ * <b>MAY</b> not be included) {@link MetaData} object.
+ * 
+ * @author niki
+ */
+abstract public class BasicLibrary {
+       /**
+        * Retrieve the main {@link File} corresponding to the given {@link Story},
+        * which can be passed to an external reader or instance.
+        * <p>
+        * Do <b>NOT</b> alter this file.
+        * 
+        * @param luid
+        *            the Library UID of the story
+        * 
+        * @return the corresponding {@link Story}
+        */
+       public abstract File getFile(String luid);
+
+       /**
+        * Return the cover image associated to this story.
+        * 
+        * @param luid
+        *            the Library UID of the story
+        * 
+        * @return the cover image
+        */
+       public abstract BufferedImage getCover(String luid);
+
+       /**
+        * Return the list of stories (represented by their {@link MetaData}, which
+        * <b>MAY</b> not have the cover included).
+        * 
+        * @param pg
+        *            the optional {@link Progress}
+        * 
+        * @return the list (can be empty but not NULL)
+        */
+       protected abstract List<MetaData> getMetas(Progress pg);
+
+       /**
+        * Invalidate the {@link Story} cache (when the content should be re-read
+        * because it was changed).
+        */
+       protected abstract void clearCache();
+
+       /**
+        * Return the next LUID that can be used.
+        * 
+        * @return the next luid
+        */
+       protected abstract int getNextId();
+
+       /**
+        * Delete the target {@link Story}.
+        * 
+        * @param luid
+        *            the LUID of the {@link Story}
+        * 
+        * @throws IOException
+        *             in case of I/O error or if the {@link Story} wa not found
+        */
+       protected abstract void doDelete(String luid) throws IOException;
+
+       /**
+        * Actually save the story to the back-end.
+        * 
+        * @param story
+        *            the {@link Story} to save
+        * @param pg
+        *            the optional {@link Progress}
+        * 
+        * @return the saved {@link Story} (which may have changed, especially
+        *         regarding the {@link MetaData})
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       protected abstract Story doSave(Story story, Progress pg)
+                       throws IOException;
+
+       /**
+        * Refresh the {@link BasicLibrary}, that is, make sure all stories are
+        * loaded.
+        * 
+        * @param full
+        *            force the full content of the stories to be loaded, not just
+        *            the {@link MetaData}
+        * 
+        * @param pg
+        *            the optional progress reporter
+        */
+       public void refresh(boolean full, Progress pg) {
+               if (full) {
+                       // TODO: progress
+                       List<MetaData> metas = getMetas(pg);
+                       for (MetaData meta : metas) {
+                               getStory(meta.getLuid(), null);
+                       }
+               } else {
+                       getMetas(pg);
+               }
+       }
+
+       /**
+        * List all the known types (sources) of stories.
+        * 
+        * @return the sources
+        */
+       public synchronized List<String> getSources() {
+               List<String> list = new ArrayList<String>();
+               for (MetaData meta : getMetas(null)) {
+                       String storySource = meta.getSource();
+                       if (!list.contains(storySource)) {
+                               list.add(storySource);
+                       }
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * List all the known authors of stories.
+        * 
+        * @return the authors
+        */
+       public synchronized List<String> getAuthors() {
+               List<String> list = new ArrayList<String>();
+               for (MetaData meta : getMetas(null)) {
+                       String storyAuthor = meta.getAuthor();
+                       if (!list.contains(storyAuthor)) {
+                               list.add(storyAuthor);
+                       }
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * List all the stories in the {@link BasicLibrary}.
+        * <p>
+        * Cover images not included.
+        * 
+        * @return the stories
+        */
+       public synchronized List<MetaData> getList() {
+               return getMetas(null);
+       }
+
+       /**
+        * List all the stories of the given source type in the {@link BasicLibrary}
+        * , or all the stories if NULL is passed as a type.
+        * <p>
+        * Cover images not included.
+        * 
+        * @param type
+        *            the type of story to retrieve, or NULL for all
+        * 
+        * @return the stories
+        */
+       public synchronized List<MetaData> getListBySource(String type) {
+               List<MetaData> list = new ArrayList<MetaData>();
+               for (MetaData meta : getMetas(null)) {
+                       String storyType = meta.getSource();
+                       if (type == null || type.equalsIgnoreCase(storyType)) {
+                               list.add(meta);
+                       }
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * List all the stories of the given author in the {@link BasicLibrary}, or
+        * all the stories if NULL is passed as an author.
+        * <p>
+        * Cover images not included.
+        * 
+        * @param author
+        *            the author of the stories to retrieve, or NULL for all
+        * 
+        * @return the stories
+        */
+       public synchronized List<MetaData> getListByAuthor(String author) {
+               List<MetaData> list = new ArrayList<MetaData>();
+               for (MetaData meta : getMetas(null)) {
+                       String storyAuthor = meta.getAuthor();
+                       if (author == null || author.equalsIgnoreCase(storyAuthor)) {
+                               list.add(meta);
+                       }
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * Retrieve a {@link MetaData} corresponding to the given {@link Story},
+        * cover image <b>MAY</b> not be included.
+        * 
+        * @param luid
+        *            the Library UID of the story
+        * 
+        * @return the corresponding {@link Story}
+        */
+       public synchronized MetaData getInfo(String luid) {
+               if (luid != null) {
+                       for (MetaData meta : getMetas(null)) {
+                               if (luid.equals(meta.getLuid())) {
+                                       return meta;
+                               }
+                       }
+               }
+
+               return null;
+       }
+
+       /**
+        * Retrieve a specific {@link Story}.
+        * 
+        * @param luid
+        *            the Library UID of the story
+        * @param pg
+        *            the optional progress reporter
+        * 
+        * @return the corresponding {@link Story} or NULL if not found
+        */
+       public synchronized Story getStory(String luid, Progress pg) {
+               // TODO: pg
+               if (pg == null) {
+                       pg = new Progress();
+               }
+
+               Story story = null;
+               for (MetaData meta : getMetas(null)) {
+                       if (meta.getLuid().equals(luid)) {
+                               File file = getFile(luid);
+                               try {
+                                       SupportType type = SupportType.valueOfAllOkUC(meta
+                                                       .getType());
+                                       URL url = file.toURI().toURL();
+                                       if (type != null) {
+                                               story = BasicSupport.getSupport(type).process(url, pg);
+                                       } else {
+                                               throw new IOException("Unknown type: " + meta.getType());
+                                       }
+                               } catch (IOException e) {
+                                       // We should not have not-supported files in the
+                                       // library
+                                       Instance.syserr(new IOException(
+                                                       "Cannot load file from library: " + file, e));
+                               } finally {
+                                       pg.done();
+                               }
+
+                               break;
+                       }
+               }
+
+               return story;
+       }
+
+       /**
+        * Import the {@link Story} at the given {@link URL} into the
+        * {@link BasicLibrary}.
+        * 
+        * @param url
+        *            the {@link URL} to import
+        * @param pg
+        *            the optional progress reporter
+        * 
+        * @return the imported {@link Story}
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public Story imprt(URL url, Progress pg) throws IOException {
+               BasicSupport support = BasicSupport.getSupport(url);
+               if (support == null) {
+                       throw new IOException("URL not supported: " + url.toString());
+               }
+
+               return save(support.process(url, pg), null);
+       }
+
+       /**
+        * Export the {@link Story} to the given target in the given format.
+        * 
+        * @param luid
+        *            the {@link Story} ID
+        * @param type
+        *            the {@link OutputType} to transform it to
+        * @param target
+        *            the target to save to
+        * @param pg
+        *            the optional progress reporter
+        * 
+        * @return the saved resource (the main saved {@link File})
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public File export(String luid, OutputType type, String target, Progress pg)
+                       throws IOException {
+               Progress pgGetStory = new Progress();
+               Progress pgOut = new Progress();
+               if (pg != null) {
+                       pg.setMax(2);
+                       pg.addProgress(pgGetStory, 1);
+                       pg.addProgress(pgOut, 1);
+               }
+
+               BasicOutput out = BasicOutput.getOutput(type, true);
+               if (out == null) {
+                       throw new IOException("Output type not supported: " + type);
+               }
+
+               Story story = getStory(luid, pgGetStory);
+               if (story == null) {
+                       throw new IOException("Cannot find story to export: " + luid);
+               }
+
+               return out.process(story, target, pgOut);
+       }
+
+       /**
+        * Save a {@link Story} to the {@link BasicLibrary}.
+        * 
+        * @param story
+        *            the {@link Story} to save
+        * @param pg
+        *            the optional progress reporter
+        * 
+        * @return the same {@link Story}, whose LUID may have changed
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public Story save(Story story, Progress pg) throws IOException {
+               return save(story, null, pg);
+       }
+
+       /**
+        * Save a {@link Story} to the {@link BasicLibrary} -- the LUID <b>must</b>
+        * be correct, or NULL to get the next free one.
+        * <p>
+        * Will override any previous {@link Story} with the same LUID.
+        * 
+        * @param story
+        *            the {@link Story} to save
+        * @param luid
+        *            the <b>correct</b> LUID or NULL to get the next free one
+        * @param pg
+        *            the optional progress reporter
+        * 
+        * @return the same {@link Story}, whose LUID may have changed
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public synchronized Story save(Story story, String luid, Progress pg)
+                       throws IOException {
+               // Do not change the original metadata, but change the original story
+               MetaData meta = story.getMeta().clone();
+               story.setMeta(meta);
+
+               if (luid == null || luid.isEmpty()) {
+                       meta.setLuid(String.format("%03d", getNextId()));
+               } else {
+                       meta.setLuid(luid);
+               }
+
+               if (getInfo(luid) != null) {
+                       delete(luid);
+               }
+               doSave(story, pg);
+
+               clearCache();
+
+               return story;
+       }
+
+       /**
+        * Delete the given {@link Story} from this {@link BasicLibrary}.
+        * 
+        * @param luid
+        *            the LUID of the target {@link Story}
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public synchronized void delete(String luid) throws IOException {
+               doDelete(luid);
+               clearCache();
+       }
+
+       /**
+        * Change the type (source) of the given {@link Story}.
+        * 
+        * @param luid
+        *            the {@link Story} LUID
+        * @param newSource
+        *            the new source
+        * @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 changeSource(String luid, String newSource,
+                       Progress pg) throws IOException {
+               MetaData meta = getInfo(luid);
+               if (meta == null) {
+                       throw new IOException("Story not found: " + luid);
+               }
+
+               meta.setSource(newSource);
+               saveMeta(meta, pg);
+       }
+
+       /**
+        * Save back the current state of the {@link MetaData} (LUID <b>MUST NOT</b>
+        * change) for this {@link Story}.
+        * <p>
+        * By default, delete the old {@link Story} then recreate a new
+        * {@link Story}.
+        * <p>
+        * Note that this behaviour can lead to data loss.
+        * 
+        * @param meta
+        *            the new {@link MetaData} (LUID <b>MUST NOT</b> change)
+        * @param pg
+        *            the optional {@link Progress}
+        * 
+        * @throws IOException
+        *             in case of I/O error or if the {@link Story} was not found
+        */
+       protected synchronized void saveMeta(MetaData meta, Progress pg)
+                       throws IOException {
+               if (pg == null) {
+                       pg = new Progress();
+               }
+
+               Progress pgGet = new Progress();
+               Progress pgSet = new Progress();
+               pg.addProgress(pgGet, 50);
+               pg.addProgress(pgSet, 50);
+
+               Story story = getStory(meta.getLuid(), pgGet);
+               if (story == null) {
+                       throw new IOException("Story not found: " + meta.getLuid());
+               }
+
+               delete(meta.getLuid());
+
+               story.setMeta(meta);
+               save(story, meta.getLuid(), pgSet);
+
+               pg.done();
+       }
+}
index 42e141e60bcc2dda445c55a77abc26dcd27a7e05..f9060f440b5e2cc9fecd95090508bfd45d692291 100644 (file)
@@ -23,7 +23,7 @@ public class Instance {
        private static UiConfigBundle uiconfig;
        private static StringIdBundle trans;
        private static Cache cache;
-       private static Library lib;
+       private static LocalLibrary lib;
        private static boolean debug;
        private static File coverDir;
        private static File readerTmp;
@@ -74,7 +74,7 @@ public class Instance {
                uiconfig = new UiConfigBundle();
                trans = new StringIdBundle(getLang());
                try {
-                       lib = new Library(getFile(Config.LIBRARY_DIR),
+                       lib = new LocalLibrary(getFile(Config.LIBRARY_DIR),
                                        OutputType.INFO_TEXT, OutputType.CBZ);
                } catch (Exception e) {
                        syserr(new IOException("Cannot create library for directory: "
@@ -161,11 +161,11 @@ public class Instance {
        }
 
        /**
-        * Get the (unique) {@link Library} for the program.
+        * Get the (unique) {@link LocalLibrary} for the program.
         * 
-        * @return the {@link Library}
+        * @return the {@link LocalLibrary}
         */
-       public static Library getLibrary() {
+       public static BasicLibrary getLibrary() {
                return lib;
        }
 
@@ -189,7 +189,7 @@ public class Instance {
 
        /**
         * Return the directory where to store temporary files for the remote
-        * {@link Library}.
+        * {@link LocalLibrary}.
         * 
         * @param host
         *            the remote for this host
diff --git a/src/be/nikiroo/fanfix/Library.java b/src/be/nikiroo/fanfix/Library.java
deleted file mode 100644 (file)
index 3868bd3..0000000
+++ /dev/null
@@ -1,710 +0,0 @@
-package be.nikiroo.fanfix;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.output.BasicOutput;
-import be.nikiroo.fanfix.output.BasicOutput.OutputType;
-import be.nikiroo.fanfix.output.InfoCover;
-import be.nikiroo.fanfix.supported.BasicSupport;
-import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
-import be.nikiroo.fanfix.supported.InfoReader;
-import be.nikiroo.utils.IOUtils;
-import be.nikiroo.utils.Progress;
-
-/**
- * Manage a library of Stories: import, export, list.
- * <p>
- * Each {@link Story} object will be associated with a (local to the library)
- * unique ID, the LUID, which will be used to identify the {@link Story}.
- * <p>
- * Most of the {@link Library} functions work on either the LUID or a partial
- * (cover not included) {@link MetaData} object.
- * 
- * @author niki
- */
-public class Library {
-       protected File baseDir;
-       protected boolean localSpeed;
-       protected Map<MetaData, File> stories;
-
-       private int lastId;
-       private OutputType text;
-       private OutputType image;
-
-       /**
-        * Create a new {@link Library} with the given backend directory.
-        * 
-        * @param dir
-        *            the directory where to find the {@link Story} objects
-        * @param text
-        *            the {@link OutputType} to save the text-focused stories into
-        * @param image
-        *            the {@link OutputType} to save the images-focused stories into
-        */
-       public Library(File dir, OutputType text, OutputType image) {
-               this();
-
-               this.baseDir = dir;
-
-               this.lastId = 0;
-               this.text = text;
-               this.image = image;
-
-               dir.mkdirs();
-       }
-
-       /**
-        * Create a new {@link Library} with no link to the local machine.
-        * <p>
-        * Reserved for extensions.
-        */
-       protected Library() {
-               this.stories = new HashMap<MetaData, File>();
-       }
-
-       /**
-        * Refresh the {@link Library}, that is, make sure all stories are loaded.
-        * 
-        * @param pg
-        *            the optional progress reporter
-        */
-       public void refresh(Progress pg) {
-               getStories(pg);
-       }
-
-       /**
-        * List all the known types (sources) of stories.
-        * 
-        * @return the types
-        */
-       public synchronized List<String> getTypes() {
-               List<String> list = new ArrayList<String>();
-               for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                       String storyType = entry.getKey().getSource();
-                       if (!list.contains(storyType)) {
-                               list.add(storyType);
-                       }
-               }
-
-               Collections.sort(list);
-               return list;
-       }
-
-       /**
-        * List all the known authors of stories.
-        * 
-        * @return the authors
-        */
-       public synchronized List<String> getAuthors() {
-               List<String> list = new ArrayList<String>();
-               for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                       String storyAuthor = entry.getKey().getAuthor();
-                       if (!list.contains(storyAuthor)) {
-                               list.add(storyAuthor);
-                       }
-               }
-
-               Collections.sort(list);
-               return list;
-       }
-
-       /**
-        * List all the stories of the given author in the {@link Library}, or all
-        * the stories if NULL is passed as an author.
-        * <p>
-        * Cover images not included.
-        * 
-        * @param author
-        *            the author of the stories to retrieve, or NULL for all
-        * 
-        * @return the stories
-        */
-       public synchronized List<MetaData> getListByAuthor(String author) {
-               List<MetaData> list = new ArrayList<MetaData>();
-               for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                       String storyAuthor = entry.getKey().getAuthor();
-                       if (author == null || author.equalsIgnoreCase(storyAuthor)) {
-                               list.add(entry.getKey());
-                       }
-               }
-
-               Collections.sort(list);
-               return list;
-       }
-
-       /**
-        * List all the stories of the given source type in the {@link Library}, or
-        * all the stories if NULL is passed as a type.
-        * <p>
-        * Cover images not included.
-        * 
-        * @param type
-        *            the type of story to retrieve, or NULL for all
-        * 
-        * @return the stories
-        */
-       public synchronized List<MetaData> getListBySource(String type) {
-               List<MetaData> list = new ArrayList<MetaData>();
-               for (MetaData meta : getStories(null).keySet()) {
-                       String storyType = meta.getSource();
-                       if (type == null || type.equalsIgnoreCase(storyType)) {
-                               list.add(meta);
-                       }
-               }
-
-               Collections.sort(list);
-               return list;
-       }
-
-       /**
-        * Retrieve a {@link MetaData} corresponding to the given {@link Story},
-        * cover image <b>MAY</b> not be included.
-        * 
-        * @param luid
-        *            the Library UID of the story
-        * 
-        * @return the corresponding {@link Story}
-        */
-       public synchronized MetaData getInfo(String luid) {
-               if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                               if (luid.equals(entry.getKey().getLuid())) {
-                                       return entry.getKey();
-                               }
-                       }
-               }
-
-               return null;
-       }
-
-       /**
-        * Retrieve a {@link File} corresponding to the given {@link Story}.
-        * 
-        * @param luid
-        *            the Library UID of the story
-        * 
-        * @return the corresponding {@link Story}
-        */
-       public synchronized File getFile(String luid) {
-               if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                               if (luid.equals(entry.getKey().getLuid())) {
-                                       return entry.getValue();
-                               }
-                       }
-               }
-
-               return null;
-       }
-
-       /**
-        * Return the cover image associated to this story.
-        * 
-        * @param luid
-        *            the Library UID of the story
-        * 
-        * @return the cover image
-        */
-       public synchronized BufferedImage getCover(String luid) {
-               MetaData meta = getInfo(luid);
-               if (meta != null) {
-                       getFile(luid); // to help remote implementation
-                       try {
-                               File infoFile = new File(getExpectedFile(meta).getPath()
-                                               + ".info");
-                               meta = readMeta(infoFile, true).getKey();
-                               return meta.getCover();
-                       } catch (IOException e) {
-                               Instance.syserr(e);
-                       }
-               }
-
-               return null;
-       }
-
-       /**
-        * Retrieve a specific {@link Story}.
-        * 
-        * @param luid
-        *            the Library UID of the story
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the corresponding {@link Story} or NULL if not found
-        */
-       public synchronized Story getStory(String luid, Progress pg) {
-               if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
-                               if (luid.equals(entry.getKey().getLuid())) {
-                                       MetaData meta = entry.getKey();
-                                       File file = getFile(luid); // to help remote implementation
-                                       try {
-                                               SupportType type = SupportType.valueOfAllOkUC(meta
-                                                               .getType());
-                                               URL url = file.toURI().toURL();
-                                               if (type != null) {
-                                                       return BasicSupport.getSupport(type).process(url,
-                                                                       pg);
-                                               } else {
-                                                       throw new IOException("Unknown type: "
-                                                                       + meta.getType());
-                                               }
-                                       } catch (IOException e) {
-                                               // We should not have not-supported files in the
-                                               // library
-                                               Instance.syserr(new IOException(
-                                                               "Cannot load file from library: " + file, e));
-                                       }
-                               }
-                       }
-               }
-
-               if (pg != null) {
-                       pg.setMinMax(0, 1);
-                       pg.setProgress(1);
-               }
-
-               return null;
-       }
-
-       /**
-        * Import the {@link Story} at the given {@link URL} into the
-        * {@link Library}.
-        * 
-        * @param url
-        *            the {@link URL} to import
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the imported {@link Story}
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public Story imprt(URL url, Progress pg) throws IOException {
-               BasicSupport support = BasicSupport.getSupport(url);
-               if (support == null) {
-                       throw new IOException("URL not supported: " + url.toString());
-               }
-
-               return save(support.process(url, pg), null);
-       }
-
-       /**
-        * Export the {@link Story} to the given target in the given format.
-        * 
-        * @param luid
-        *            the {@link Story} ID
-        * @param type
-        *            the {@link OutputType} to transform it to
-        * @param target
-        *            the target to save to
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the saved resource (the main saved {@link File})
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public File export(String luid, OutputType type, String target, Progress pg)
-                       throws IOException {
-               Progress pgGetStory = new Progress();
-               Progress pgOut = new Progress();
-               if (pg != null) {
-                       pg.setMax(2);
-                       pg.addProgress(pgGetStory, 1);
-                       pg.addProgress(pgOut, 1);
-               }
-
-               BasicOutput out = BasicOutput.getOutput(type, true);
-               if (out == null) {
-                       throw new IOException("Output type not supported: " + type);
-               }
-
-               Story story = getStory(luid, pgGetStory);
-               if (story == null) {
-                       throw new IOException("Cannot find story to export: " + luid);
-               }
-
-               return out.process(story, target, pgOut);
-       }
-
-       /**
-        * Save a {@link Story} to the {@link Library}.
-        * 
-        * @param story
-        *            the {@link Story} to save
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the same {@link Story}, whose LUID may have changed
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public Story save(Story story, Progress pg) throws IOException {
-               return save(story, null, pg);
-       }
-
-       /**
-        * Save a {@link Story} to the {@link Library} -- the LUID <b>must</b> be
-        * correct, or NULL to get the next free one.
-        * 
-        * @param story
-        *            the {@link Story} to save
-        * @param luid
-        *            the <b>correct</b> LUID or NULL to get the next free one
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the same {@link Story}, whose LUID may have changed
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public synchronized Story save(Story story, String luid, Progress pg)
-                       throws IOException {
-               // Do not change the original metadata, but change the original story
-               MetaData key = story.getMeta().clone();
-               story.setMeta(key);
-
-               if (luid == null || luid.isEmpty()) {
-                       getStories(null); // refresh lastId if needed
-                       key.setLuid(String.format("%03d", (++lastId)));
-               } else {
-                       key.setLuid(luid);
-               }
-
-               getExpectedDir(key.getSource()).mkdirs();
-               if (!getExpectedDir(key.getSource()).exists()) {
-                       throw new IOException("Cannot create library dir");
-               }
-
-               OutputType out;
-               if (key != null && key.isImageDocument()) {
-                       out = image;
-               } else {
-                       out = text;
-               }
-
-               BasicOutput it = BasicOutput.getOutput(out, true);
-               it.process(story, getExpectedFile(key).getPath(), pg);
-
-               // empty cache
-               stories.clear();
-
-               return story;
-       }
-
-       /**
-        * Delete the given {@link Story} from this {@link Library}.
-        * 
-        * @param luid
-        *            the LUID of the target {@link Story}
-        * 
-        * @return TRUE if it was deleted
-        */
-       public synchronized boolean delete(String luid) {
-               boolean ok = false;
-
-               List<File> files = getFiles(luid);
-               if (!files.isEmpty()) {
-                       for (File file : files) {
-                               IOUtils.deltree(file);
-                       }
-
-                       ok = true;
-
-                       // clear cache
-                       stories.clear();
-               }
-
-               return ok;
-       }
-
-       /**
-        * Change the type (source) of the given {@link Story}.
-        * 
-        * @param luid
-        *            the {@link Story} LUID
-        * @param newType
-        *            the new type
-        * 
-        * @return TRUE if the {@link Story} was found
-        */
-       public synchronized boolean changeType(String luid, String newType) {
-               MetaData meta = getInfo(luid);
-               if (meta != null) {
-                       meta.setSource(newType);
-                       File newDir = getExpectedDir(meta.getSource());
-                       if (!newDir.exists()) {
-                               newDir.mkdir();
-                       }
-
-                       List<File> files = getFiles(luid);
-                       for (File file : files) {
-                               if (file.getName().endsWith(".info")) {
-                                       try {
-                                               String name = file.getName().replaceFirst("\\.info$",
-                                                               "");
-                                               InfoCover.writeInfo(newDir, name, meta);
-                                               file.delete();
-                                       } catch (IOException e) {
-                                               Instance.syserr(e);
-                                       }
-                               } else {
-                                       file.renameTo(new File(newDir, file.getName()));
-                               }
-                       }
-
-                       // clear cache
-                       stories.clear();
-
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * The library is accessed locally or at local speed (for operations like
-        * {@link Library#getFile(String)}).
-        * <p>
-        * It could be cached, too, it is only about the access speed.
-        * 
-        * @return TRUE if it is accessed locally
-        */
-       public boolean isLocalSpeed() {
-               return localSpeed;
-       }
-
-       /**
-        * Return the list of files/dirs on disk for this {@link Story}.
-        * <p>
-        * If the {@link Story} is not found, and empty list is returned.
-        * 
-        * @param luid
-        *            the {@link Story} LUID
-        * 
-        * @return the list of {@link File}s
-        */
-       private List<File> getFiles(String luid) {
-               List<File> files = new ArrayList<File>();
-
-               MetaData meta = getInfo(luid);
-               File file = getFile(luid); // to help remote implementation
-
-               if (file != null) {
-                       files.add(file);
-
-                       String readerExt = getOutputType(meta).getDefaultExtension(true);
-                       String fileExt = getOutputType(meta).getDefaultExtension(false);
-
-                       String path = file.getAbsolutePath();
-                       if (readerExt != null && !readerExt.equals(fileExt)) {
-                               path = path.substring(0, path.length() - readerExt.length())
-                                               + fileExt;
-                               file = new File(path);
-
-                               if (file.exists()) {
-                                       files.add(file);
-                               }
-                       }
-
-                       File infoFile = new File(path + ".info");
-                       if (!infoFile.exists()) {
-                               infoFile = new File(path.substring(0,
-                                               path.length() - fileExt.length())
-                                               + ".info");
-                       }
-
-                       if (infoFile.exists()) {
-                               files.add(infoFile);
-                       }
-
-                       String coverExt = "."
-                                       + Instance.getConfig().getString(Config.IMAGE_FORMAT_COVER);
-                       File coverFile = new File(path + coverExt);
-                       if (!coverFile.exists()) {
-                               coverFile = new File(path.substring(0,
-                                               path.length() - fileExt.length())
-                                               + coverExt);
-                       }
-
-                       if (coverFile.exists()) {
-                               files.add(coverFile);
-                       }
-               }
-
-               return files;
-       }
-
-       /**
-        * The directory (full path) where the {@link Story} related to this
-        * {@link MetaData} should be located on disk.
-        * 
-        * @param type
-        *            the type (source)
-        * 
-        * @return the target directory
-        */
-       private File getExpectedDir(String type) {
-               String source = type.replaceAll("[^a-zA-Z0-9._+-]", "_");
-               return new File(baseDir, source);
-       }
-
-       /**
-        * The target (full path) where the {@link Story} related to this
-        * {@link MetaData} should be located on disk.
-        * 
-        * @param key
-        *            the {@link Story} {@link MetaData}
-        * 
-        * @return the target
-        */
-       private File getExpectedFile(MetaData key) {
-               String title = key.getTitle();
-               if (title == null) {
-                       title = "";
-               }
-               title = title.replaceAll("[^a-zA-Z0-9._+-]", "_");
-               return new File(getExpectedDir(key.getSource()), key.getLuid() + "_"
-                               + title);
-       }
-
-       /**
-        * Return all the known stories in this {@link Library} object.
-        * 
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @return the stories
-        */
-       protected synchronized Map<MetaData, File> getStories(Progress pg) {
-               if (pg == null) {
-                       pg = new Progress();
-               } else {
-                       pg.setMinMax(0, 100);
-               }
-
-               if (stories.isEmpty()) {
-                       lastId = 0;
-
-                       File[] dirs = baseDir.listFiles(new FileFilter() {
-                               public boolean accept(File file) {
-                                       return file != null && file.isDirectory();
-                               }
-                       });
-
-                       Progress pgDirs = new Progress(0, 100 * dirs.length);
-                       pg.addProgress(pgDirs, 100);
-
-                       for (File dir : dirs) {
-                               File[] files = dir.listFiles(new FileFilter() {
-                                       public boolean accept(File file) {
-                                               return file != null
-                                                               && file.getPath().toLowerCase()
-                                                                               .endsWith(".info");
-                                       }
-                               });
-
-                               Progress pgFiles = new Progress(0, files.length);
-                               pgDirs.addProgress(pgFiles, 100);
-                               pgDirs.setName("Loading from: " + dir.getName());
-
-                               for (File file : files) {
-                                       pgFiles.setName(file.getName());
-                                       try {
-                                               Entry<MetaData, File> entry = readMeta(file, false);
-                                               try {
-                                                       int id = Integer.parseInt(entry.getKey().getLuid());
-                                                       if (id > lastId) {
-                                                               lastId = id;
-                                                       }
-
-                                                       stories.put(entry.getKey(), entry.getValue());
-                                               } catch (Exception e) {
-                                                       // not normal!!
-                                                       throw new IOException(
-                                                                       "Cannot understand the LUID of "
-                                                                                       + file.getPath() + ": "
-                                                                                       + entry.getKey().getLuid(), e);
-                                               }
-                                       } catch (IOException e) {
-                                               // We should not have not-supported files in the
-                                               // library
-                                               Instance.syserr(new IOException(
-                                                               "Cannot load file from library: "
-                                                                               + file.getPath(), e));
-                                       }
-                                       pgFiles.add(1);
-                               }
-
-                               pgFiles.setName(null);
-                       }
-
-                       pgDirs.setName("Loading directories");
-               }
-
-               return stories;
-       }
-
-       private Entry<MetaData, File> readMeta(File infoFile, boolean withCover)
-                       throws IOException {
-
-               final MetaData meta = InfoReader.readMeta(infoFile, withCover);
-
-               // Replace .info with whatever is needed:
-               String path = infoFile.getPath();
-               path = path.substring(0, path.length() - ".info".length());
-
-               String newExt = getOutputType(meta).getDefaultExtension(true);
-
-               File targetFile = new File(path + newExt);
-
-               final File ffile = targetFile;
-               return new Entry<MetaData, File>() {
-                       public File setValue(File value) {
-                               return null;
-                       }
-
-                       public File getValue() {
-                               return ffile;
-                       }
-
-                       public MetaData getKey() {
-                               return meta;
-                       }
-               };
-       }
-
-       /**
-        * Return the {@link OutputType} for this {@link Story}.
-        * 
-        * @param meta
-        *            the {@link Story} {@link MetaData}
-        * 
-        * @return the type
-        */
-       private OutputType getOutputType(MetaData meta) {
-               if (meta != null && meta.isImageDocument()) {
-                       return image;
-               } else {
-                       return text;
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/LocalLibrary.java b/src/be/nikiroo/fanfix/LocalLibrary.java
new file mode 100644 (file)
index 0000000..35f13f0
--- /dev/null
@@ -0,0 +1,362 @@
+package be.nikiroo.fanfix;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import be.nikiroo.fanfix.bundles.Config;
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.fanfix.output.BasicOutput;
+import be.nikiroo.fanfix.output.BasicOutput.OutputType;
+import be.nikiroo.fanfix.output.InfoCover;
+import be.nikiroo.fanfix.supported.InfoReader;
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.Progress;
+
+/**
+ * This {@link BasicLibrary} will store the stories locally on disk.
+ * 
+ * @author niki
+ */
+public class LocalLibrary extends BasicLibrary {
+       private int lastId;
+       private Map<MetaData, File[]> stories; // Files: [ infoFile, TargetFile ]
+
+       private File baseDir;
+       private OutputType text;
+       private OutputType image;
+
+       /**
+        * Create a new {@link LocalLibrary} with the given back-end directory.
+        * 
+        * @param baseDir
+        *            the directory where to find the {@link Story} objects
+        * @param text
+        *            the {@link OutputType} to save the text-focused stories into
+        * @param image
+        *            the {@link OutputType} to save the images-focused stories into
+        */
+       public LocalLibrary(File baseDir, OutputType text, OutputType image) {
+               this.baseDir = baseDir;
+               this.text = text;
+               this.image = image;
+
+               this.lastId = 0;
+               this.stories = null;
+
+               baseDir.mkdirs();
+       }
+
+       @Override
+       protected List<MetaData> getMetas(Progress pg) {
+               return new ArrayList<MetaData>(getStories(pg).keySet());
+       }
+
+       @Override
+       public File getFile(String luid) {
+               File[] files = getStories(null).get(getInfo(luid));
+               if (files != null) {
+                       return files[1];
+               }
+
+               return null;
+       }
+
+       @Override
+       public BufferedImage getCover(String luid) {
+               MetaData meta = getInfo(luid);
+               if (meta != null) {
+                       File[] files = getStories(null).get(meta);
+                       if (files != null) {
+                               File infoFile = files[0];
+
+                               try {
+                                       meta = InfoReader.readMeta(infoFile, true);
+                                       return meta.getCover();
+                               } catch (IOException e) {
+                                       Instance.syserr(e);
+                               }
+                       }
+               }
+
+               return null;
+       }
+
+       @Override
+       protected void clearCache() {
+               stories = null;
+       }
+
+       @Override
+       protected synchronized int getNextId() {
+               return ++lastId;
+       }
+
+       @Override
+       protected void doDelete(String luid) throws IOException {
+               for (File file : getRelatedFiles(luid)) {
+                       // TODO: throw an IOException if we cannot delete the files?
+                       IOUtils.deltree(file);
+               }
+       }
+
+       @Override
+       protected Story doSave(Story story, Progress pg) throws IOException {
+               MetaData meta = story.getMeta();
+
+               File expectedTarget = getExpectedFile(meta);
+               expectedTarget.getParentFile().mkdirs();
+
+               BasicOutput it = BasicOutput.getOutput(getOutputType(meta), true);
+               it.process(story, expectedTarget.getPath(), pg);
+
+               return story;
+       }
+
+       @Override
+       protected synchronized void saveMeta(MetaData meta, Progress pg)
+                       throws IOException {
+               File newDir = getExpectedDir(meta.getSource());
+               if (!newDir.exists()) {
+                       newDir.mkdir();
+               }
+
+               List<File> relatedFiles = getRelatedFiles(meta.getLuid());
+               for (File relatedFile : relatedFiles) {
+                       // TODO: this is not safe at all.
+                       // We should copy all the files THEN delete them
+                       // Maybe also adding some rollback cleanup if possible
+                       if (relatedFile.getName().endsWith(".info")) {
+                               try {
+                                       String name = relatedFile.getName().replaceFirst(
+                                                       "\\.info$", "");
+                                       InfoCover.writeInfo(newDir, name, meta);
+                                       relatedFile.delete();
+                               } catch (IOException e) {
+                                       Instance.syserr(e);
+                               }
+                       } else {
+                               relatedFile.renameTo(new File(newDir, relatedFile.getName()));
+                       }
+               }
+
+               clearCache();
+       }
+
+       /**
+        * Return the {@link OutputType} for this {@link Story}.
+        * 
+        * @param meta
+        *            the {@link Story} {@link MetaData}
+        * 
+        * @return the type
+        */
+       private OutputType getOutputType(MetaData meta) {
+               if (meta != null && meta.isImageDocument()) {
+                       return image;
+               } else {
+                       return text;
+               }
+       }
+
+       /**
+        * Get the target {@link File} related to the given <tt>.info</tt>
+        * {@link File} and {@link MetaData}.
+        * 
+        * @param meta
+        *            the meta
+        * @param infoFile
+        *            the <tt>.info</tt> {@link File}
+        * 
+        * @return the target {@link File}
+        */
+       private File getTargetFile(MetaData meta, File infoFile) {
+               // Replace .info with whatever is needed:
+               String path = infoFile.getPath();
+               path = path.substring(0, path.length() - ".info".length());
+               String newExt = getOutputType(meta).getDefaultExtension(true);
+
+               return new File(path + newExt);
+       }
+
+       /**
+        * The target (full path) where the {@link Story} related to this
+        * {@link MetaData} should be located on disk for a new {@link Story}.
+        * 
+        * @param key
+        *            the {@link Story} {@link MetaData}
+        * 
+        * @return the target
+        */
+       private File getExpectedFile(MetaData key) {
+               String title = key.getTitle();
+               if (title == null) {
+                       title = "";
+               }
+               title = title.replaceAll("[^a-zA-Z0-9._+-]", "_");
+               return new File(getExpectedDir(key.getSource()), key.getLuid() + "_"
+                               + title);
+       }
+
+       /**
+        * The directory (full path) where the new {@link Story} related to this
+        * {@link MetaData} should be located on disk.
+        * 
+        * @param type
+        *            the type (source)
+        * 
+        * @return the target directory
+        */
+       private File getExpectedDir(String type) {
+               String source = type.replaceAll("[^a-zA-Z0-9._+-]", "_");
+               return new File(baseDir, source);
+       }
+
+       /**
+        * Return the list of files/directories on disk for this {@link Story}.
+        * <p>
+        * If the {@link Story} is not found, and empty list is returned.
+        * 
+        * @param luid
+        *            the {@link Story} LUID
+        * 
+        * @return the list of {@link File}s
+        * 
+        * @throws IOException
+        *             if the {@link Story} was not found
+        */
+       private List<File> getRelatedFiles(String luid) throws IOException {
+               List<File> files = new ArrayList<File>();
+
+               MetaData meta = getInfo(luid);
+               if (meta == null) {
+                       throw new IOException("Story not found: " + luid);
+               } else {
+                       File infoFile = getStories(null).get(meta)[0];
+                       File targetFile = getStories(null).get(meta)[1];
+
+                       files.add(infoFile);
+                       files.add(targetFile);
+
+                       String readerExt = getOutputType(meta).getDefaultExtension(true);
+                       String fileExt = getOutputType(meta).getDefaultExtension(false);
+
+                       String path = targetFile.getAbsolutePath();
+                       if (readerExt != null && !readerExt.equals(fileExt)) {
+                               path = path.substring(0, path.length() - readerExt.length())
+                                               + fileExt;
+                               File relatedFile = new File(path);
+
+                               if (relatedFile.exists()) {
+                                       files.add(relatedFile);
+                               }
+                       }
+
+                       String coverExt = "."
+                                       + Instance.getConfig().getString(Config.IMAGE_FORMAT_COVER);
+                       File coverFile = new File(path + coverExt);
+                       if (!coverFile.exists()) {
+                               coverFile = new File(path.substring(0,
+                                               path.length() - fileExt.length())
+                                               + coverExt);
+                       }
+
+                       if (coverFile.exists()) {
+                               files.add(coverFile);
+                       }
+               }
+
+               return files;
+       }
+
+       /**
+        * Fill the list of stories by reading the content of the local directory
+        * {@link LocalLibrary#baseDir}.
+        * <p>
+        * Will use a cached list when possible (see
+        * {@link BasicLibrary#clearCache()}).
+        * 
+        * @param pg
+        *            the optional {@link Progress}
+        * 
+        * @return the list of stories
+        */
+       private synchronized Map<MetaData, File[]> getStories(Progress pg) {
+               if (pg == null) {
+                       pg = new Progress();
+               } else {
+                       pg.setMinMax(0, 100);
+               }
+
+               if (stories == null) {
+                       stories = new HashMap<MetaData, File[]>();
+
+                       lastId = 0;
+
+                       File[] dirs = baseDir.listFiles(new FileFilter() {
+                               public boolean accept(File file) {
+                                       return file != null && file.isDirectory();
+                               }
+                       });
+
+                       Progress pgDirs = new Progress(0, 100 * dirs.length);
+                       pg.addProgress(pgDirs, 100);
+
+                       for (File dir : dirs) {
+                               File[] infoFiles = dir.listFiles(new FileFilter() {
+                                       public boolean accept(File file) {
+                                               return file != null
+                                                               && file.getPath().toLowerCase()
+                                                                               .endsWith(".info");
+                                       }
+                               });
+
+                               Progress pgFiles = new Progress(0, infoFiles.length);
+                               pgDirs.addProgress(pgFiles, 100);
+                               pgDirs.setName("Loading from: " + dir.getName());
+
+                               for (File infoFile : infoFiles) {
+                                       pgFiles.setName(infoFile.getName());
+                                       try {
+                                               MetaData meta = InfoReader.readMeta(infoFile, false);
+                                               try {
+                                                       int id = Integer.parseInt(meta.getLuid());
+                                                       if (id > lastId) {
+                                                               lastId = id;
+                                                       }
+
+                                                       stories.put(meta, new File[] { infoFile,
+                                                                       getTargetFile(meta, infoFile) });
+                                               } catch (Exception e) {
+                                                       // not normal!!
+                                                       throw new IOException(
+                                                                       "Cannot understand the LUID of "
+                                                                                       + infoFile
+                                                                                       + ": "
+                                                                                       + (meta == null ? "[meta is NULL]"
+                                                                                                       : meta.getLuid()), e);
+                                               }
+                                       } catch (IOException e) {
+                                               // We should not have not-supported files in the
+                                               // library
+                                               Instance.syserr(new IOException(
+                                                               "Cannot load file from library: " + infoFile, e));
+                                       }
+                                       pgFiles.add(1);
+                               }
+
+                               pgFiles.setName(null);
+                       }
+
+                       pgDirs.setName("Loading directories");
+               }
+
+               return stories;
+       }
+}
index 38f58af3814fcc484f966112af59ec4f96618129..a05df434fb81fdf8edbc57ce391ec78de940678b 100644 (file)
@@ -181,12 +181,8 @@ public class Main {
                                        host = args[i];
                                } else if (port == null) {
                                        port = Integer.parseInt(args[i]);
-                                       try {
-                                               BasicReader.setDefaultLibrary(new RemoteLibrary(host,
-                                                               port));
-                                       } catch (IOException e) {
-                                               Instance.syserr(e);
-                                       }
+                                       BasicReader
+                                                       .setDefaultLibrary(new RemoteLibrary(host, port));
                                        action = MainAction.START;
                                } else {
                                        exitCode = 255;
@@ -307,7 +303,7 @@ public class Main {
        }
 
        /**
-        * Import the given resource into the {@link Library}.
+        * Import the given resource into the {@link LocalLibrary}.
         * 
         * @param urlString
         *            the resource to import
@@ -331,7 +327,8 @@ public class Main {
        }
 
        /**
-        * Export the {@link Story} from the {@link Library} to the given target.
+        * Export the {@link Story} from the {@link LocalLibrary} to the given
+        * target.
         * 
         * @param luid
         *            the story LUID
@@ -364,8 +361,8 @@ public class Main {
        }
 
        /**
-        * List the stories of the given source from the {@link Library} (unless
-        * NULL is passed, in which case all stories will be listed).
+        * List the stories of the given source from the {@link LocalLibrary}
+        * (unless NULL is passed, in which case all stories will be listed).
         * 
         * @param source
         *            the source to list the known stories of, or NULL to list all
@@ -382,8 +379,8 @@ public class Main {
         * Start the CLI reader for this {@link Story}.
         * 
         * @param story
-        *            the LUID of the {@link Story} in the {@link Library} <b>or</b>
-        *            the {@link Story} {@link URL}
+        *            the LUID of the {@link Story} in the {@link LocalLibrary}
+        *            <b>or</b> the {@link Story} {@link URL}
         * @param chapString
         *            which {@link Chapter} to read (starting at 1), or NULL to get
         *            the {@link Story} description
index cc78be906004d3adb76960427173054a86289d2c..5c111ca1a24cf718833503015c7b83e0ae7cba47 100644 (file)
@@ -1,8 +1,10 @@
 package be.nikiroo.fanfix;
 
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Story;
@@ -11,56 +13,57 @@ import be.nikiroo.utils.Progress;
 import be.nikiroo.utils.Version;
 import be.nikiroo.utils.serial.ConnectActionClient;
 
-public class RemoteLibrary extends Library {
+/**
+ * This {@link BasicLibrary} will access a remote server to list the available
+ * stories, and download the one you try to load to the local directory
+ * specified in the configuration.
+ * 
+ * @author niki
+ */
+public class RemoteLibrary extends BasicLibrary {
        private String host;
        private int port;
-
-       private Library lib;
-
-       public RemoteLibrary(String host, int port) throws IOException {
+       private File baseDir;
+
+       private LocalLibrary lib;
+       private List<MetaData> metas;
+
+       /**
+        * Create a {@link RemoteLibrary} linked to the given server.
+        * 
+        * @param host
+        *            the host to contact or NULL for localhost
+        * @param port
+        *            the port to contact it on
+        */
+       public RemoteLibrary(String host, int port) {
                this.host = host;
                this.port = port;
 
-               this.localSpeed = false;
                this.baseDir = Instance.getRemoteDir(host);
                this.baseDir.mkdirs();
 
-               this.lib = new Library(baseDir, OutputType.INFO_TEXT, OutputType.CBZ);
+               this.lib = new LocalLibrary(baseDir, OutputType.INFO_TEXT,
+                               OutputType.CBZ);
        }
 
        @Override
-       public synchronized Story save(Story story, String luid, Progress pg)
-                       throws IOException {
-               throw new java.lang.InternalError(
-                               "No write support allowed on remote Libraries");
-       }
+       protected List<MetaData> getMetas(Progress pg) {
+               // TODO: progress
 
-       @Override
-       public synchronized boolean delete(String luid) {
-               throw new java.lang.InternalError(
-                               "No write support allowed on remote Libraries");
-       }
+               if (metas == null) {
+                       metas = new ArrayList<MetaData>();
 
-       @Override
-       public synchronized boolean changeType(String luid, String newType) {
-               throw new java.lang.InternalError(
-                               "No write support allowed on remote Libraries");
-       }
-
-       @Override
-       protected synchronized Map<MetaData, File> getStories(Progress pg) {
-               // TODO: progress
-               if (stories.isEmpty()) {
                        try {
                                new ConnectActionClient(host, port, true, null) {
                                        public void action(Version serverVersion) throws Exception {
                                                try {
                                                        Object rep = send("GET_METADATA *");
                                                        for (MetaData meta : (MetaData[]) rep) {
-                                                               stories.put(meta, null);
+                                                               metas.add(meta);
                                                        }
                                                } catch (Exception e) {
-                                                       e.printStackTrace();
+                                                       Instance.syserr(e);
                                                }
                                        }
                                }.connect();
@@ -69,7 +72,7 @@ public class RemoteLibrary extends Library {
                        }
                }
 
-               return stories;
+               return metas;
        }
 
        @Override
@@ -101,9 +104,50 @@ public class RemoteLibrary extends Library {
 
                if (file != null) {
                        MetaData meta = getInfo(luid);
-                       stories.put(meta, file);
+                       metas.add(meta);
                }
 
                return file;
        }
+
+       @Override
+       public BufferedImage getCover(String luid) {
+               // Retrieve it from the network if needed:
+               if (lib.getInfo(luid) == null) {
+                       getFile(luid);
+               }
+
+               return lib.getCover(luid);
+       }
+
+       @Override
+       protected void clearCache() {
+               metas = null;
+               lib.clearCache();
+       }
+
+       @Override
+       public synchronized Story save(Story story, String luid, Progress pg)
+                       throws IOException {
+               throw new java.lang.InternalError(
+                               "No write support allowed on remote Libraries");
+       }
+
+       @Override
+       protected int getNextId() {
+               throw new java.lang.InternalError(
+                               "No write support allowed on remote Libraries");
+       }
+
+       @Override
+       protected void doDelete(String luid) throws IOException {
+               throw new java.lang.InternalError(
+                               "No write support allowed on remote Libraries");
+       }
+
+       @Override
+       protected Story doSave(Story story, Progress pg) throws IOException {
+               throw new java.lang.InternalError(
+                               "No write support allowed on remote Libraries");
+       }
 }
index 9ecbc41ef0d413608479eda2f3bbc5768591d4d7..e47a5912c9d4f6ebad55a6fba727ee38134c52c0 100644 (file)
@@ -2,6 +2,7 @@ package be.nikiroo.fanfix;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 import be.nikiroo.fanfix.data.MetaData;
@@ -35,9 +36,8 @@ public class RemoteLibraryServer extends Server {
                if (command != null) {
                        if (command.equals("GET_METADATA")) {
                                if (args != null && args.equals("*")) {
-                                       Map<MetaData, File> stories = Instance.getLibrary()
-                                                       .getStories(null);
-                                       return stories.keySet().toArray(new MetaData[] {});
+                                       List<MetaData> metas = Instance.getLibrary().getMetas(null);
+                                       return metas.toArray(new MetaData[] {});
                                }
                        } else if (command.equals("GET_STORY")) {
                                if (args != null) {
index 4423974f6c014d7a7db92c9d9fe7dc3079051a6e..eadaa5a70cebe209b6119918f5f21fc023aa7f41 100644 (file)
@@ -3,7 +3,7 @@
  * which to retrieve stories, then process them into <tt>epub</tt> (or other)
  * files that you can read anywhere.
  * <p>
- * It has support for a {@link be.nikiroo.fanfix.Library} system, too.
+ * It has support for a {@link be.nikiroo.fanfix.BasicLibrary} system, too.
  * 
  * @author niki
  */
index 05e3e3df1124b6eae858e536e5b57adec2db78e2..f8341ae0b10d32cc75eba966ed116561749ea7cf 100644 (file)
@@ -6,8 +6,9 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 
+import be.nikiroo.fanfix.BasicLibrary;
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.MetaData;
@@ -49,10 +50,10 @@ public abstract class BasicReader {
                }
        }
 
-       private static Library defaultLibrary = Instance.getLibrary();
+       private static BasicLibrary defaultLibrary = Instance.getLibrary();
        private static ReaderType defaultType = ReaderType.GUI;
 
-       private Library lib;
+       private BasicLibrary lib;
        private Story story;
        private ReaderType type;
 
@@ -101,12 +102,12 @@ public abstract class BasicReader {
        }
 
        /**
-        * The {@link Library} to load the stories from (by default, takes the
-        * default {@link Library}).
+        * The {@link LocalLibrary} to load the stories from (by default, takes the
+        * default {@link LocalLibrary}).
         * 
-        * @return the {@link Library}
+        * @return the {@link LocalLibrary}
         */
-       public Library getLibrary() {
+       public BasicLibrary getLibrary() {
                if (lib == null) {
                        lib = defaultLibrary;
                }
@@ -115,19 +116,19 @@ public abstract class BasicReader {
        }
 
        /**
-        * Change the {@link Library} that will be managed by this
+        * Change the {@link LocalLibrary} that will be managed by this
         * {@link BasicReader}.
         * 
         * @param lib
-        *            the new {@link Library}
+        *            the new {@link LocalLibrary}
         */
-       public void setLibrary(Library lib) {
+       public void setLibrary(LocalLibrary lib) {
                this.lib = lib;
        }
 
        /**
         * Create a new {@link BasicReader} for a {@link Story} in the
-        * {@link Library}.
+        * {@link LocalLibrary}.
         * 
         * @param luid
         *            the {@link Story} ID
@@ -247,12 +248,13 @@ public abstract class BasicReader {
        }
 
        /**
-        * Change the default {@link Library} to open with the {@link BasicReader}s.
+        * Change the default {@link LocalLibrary} to open with the
+        * {@link BasicReader}s.
         * 
         * @param lib
-        *            the new {@link Library}
+        *            the new {@link LocalLibrary}
         */
-       public static void setDefaultLibrary(Library lib) {
+       public static void setDefaultLibrary(BasicLibrary lib) {
                BasicReader.defaultLibrary = lib;
        }
 
@@ -285,7 +287,7 @@ public abstract class BasicReader {
        }
 
        // open with external player the related file
-       public static void open(Library lib, String luid) throws IOException {
+       public static void open(BasicLibrary lib, String luid) throws IOException {
                MetaData meta = lib.getInfo(luid);
                File target = lib.getFile(luid);
 
index 6c6150318b38917958579a4fbaf87a5f1489ff05..593f58b2122685034d3ad7b34e4c230467e1ed0f 100644 (file)
@@ -13,7 +13,7 @@ import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkListener;
 
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.VersionCheck;
 import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.Story;
@@ -25,7 +25,7 @@ import be.nikiroo.utils.ui.UIUtils;
 class LocalReader extends BasicReader {
        static private boolean nativeLookLoaded;
 
-       private Library localLibrary;
+       private LocalLibrary localLibrary;
 
        public LocalReader() throws IOException {
                if (!nativeLookLoaded) {
@@ -66,7 +66,7 @@ class LocalReader extends BasicReader {
                                                        key, value), e);
                }
 
-               localLibrary = new Library(dir, text, images);
+               localLibrary = new LocalLibrary(dir, text, images);
        }
 
        @Override
@@ -197,13 +197,21 @@ class LocalReader extends BasicReader {
 
        // delete from local reader library
        void clearLocalReaderCache(String luid) {
-               localLibrary.delete(luid);
+               try {
+                       localLibrary.delete(luid);
+               } catch (IOException e) {
+                       Instance.syserr(e);
+               }
        }
 
        // delete from main library
        void delete(String luid) {
-               localLibrary.delete(luid);
-               Instance.getLibrary().delete(luid);
+               try {
+                       localLibrary.delete(luid);
+                       Instance.getLibrary().delete(luid);
+               } catch (IOException e) {
+                       Instance.syserr(e);
+               }
        }
 
        // open the given book
@@ -218,7 +226,11 @@ class LocalReader extends BasicReader {
        }
 
        void changeType(String luid, String newType) {
-               localLibrary.changeType(luid, newType);
-               Instance.getLibrary().changeType(luid, newType);
+               try {
+                       localLibrary.changeSource(luid, newType, null);
+                       Instance.getLibrary().changeSource(luid, newType, null);
+               } catch (IOException e) {
+                       Instance.syserr(e);
+               }
        }
 }
index edd1fb60e598d1954b21bfb6f00b6317aeed5757..bf94f26f8d7ceb1a197a7ab588e33fd4b1652fc9 100644 (file)
@@ -34,7 +34,7 @@ import javax.swing.filechooser.FileFilter;
 import javax.swing.filechooser.FileNameExtensionFilter;
 
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.MetaData;
@@ -71,7 +71,7 @@ class LocalReaderFrame extends JFrame {
         * 
         * @param reader
         *            the associated {@link LocalReader} to forward some commands
-        *            and access its {@link Library}
+        *            and access its {@link LocalLibrary}
         * @param type
         *            the type of {@link Story} to load, or NULL for all types
         */
@@ -125,7 +125,7 @@ class LocalReaderFrame extends JFrame {
                final String typeF = type;
                outOfUi(pg, new Runnable() {
                        public void run() {
-                               Instance.getLibrary().refresh(pg);
+                               Instance.getLibrary().refresh(false, pg);
                                invalidate();
                                setJMenuBar(createMenu());
                                addBookPane(typeF, true);
@@ -151,7 +151,7 @@ class LocalReaderFrame extends JFrame {
        private void addBookPane(String value, boolean type) {
                if (value == null) {
                        if (type) {
-                               for (String tt : Instance.getLibrary().getTypes()) {
+                               for (String tt : Instance.getLibrary().getSources()) {
                                        if (tt != null) {
                                                addBookPane(tt, type);
                                        }
@@ -312,7 +312,7 @@ class LocalReaderFrame extends JFrame {
                JMenu sources = new JMenu("Sources");
                sources.setMnemonic(KeyEvent.VK_S);
 
-               List<String> tt = Instance.getLibrary().getTypes();
+               List<String> tt = Instance.getLibrary().getSources();
                tt.add(0, null);
                for (final String type : tt) {
                        JMenuItem item = new JMenuItem(type == null ? "All" : type);
@@ -542,7 +542,7 @@ class LocalReaderFrame extends JFrame {
 
                List<String> types = new ArrayList<String>();
                types.add(null);
-               types.addAll(Instance.getLibrary().getTypes());
+               types.addAll(Instance.getLibrary().getSources());
 
                for (String type : types) {
                        JMenuItem item = new JMenuItem(type == null ? "New type..." : type);
@@ -724,7 +724,7 @@ class LocalReaderFrame extends JFrame {
        }
 
        /**
-        * Import a {@link Story} into the main {@link Library}.
+        * Import a {@link Story} into the main {@link LocalLibrary}.
         * <p>
         * Should be called inside the UI thread.
         * 
@@ -764,7 +764,7 @@ class LocalReaderFrame extends JFrame {
        }
 
        /**
-        * Actually import the {@link Story} into the main {@link Library}.
+        * Actually import the {@link Story} into the main {@link LocalLibrary}.
         * <p>
         * Should be called inside the UI thread.
         * 
index d00bbc29079b906e0ecbeae5e45b21bca8ac7d0e..12f048240a05f0d19d116eec3c7df4c82c61a57a 100644 (file)
@@ -4,7 +4,7 @@ import java.io.IOException;
 import java.util.List;
 
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.data.MetaData;
 
 class TuiReader extends BasicReader {
index 0d47cde4a680cae672b04346194ab0b112ffd406..f556a4b7a77edefd8e808a692dc1111f4fe59a79 100644 (file)
@@ -12,15 +12,16 @@ import jexer.TLabel;
 import jexer.TText;
 import jexer.TWindow;
 import jexer.event.TResizeEvent;
+import be.nikiroo.fanfix.BasicLibrary;
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.data.Chapter;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
 import be.nikiroo.fanfix.data.Story;
 
 public class TuiReaderStoryWindow extends TWindow {
-       private Library lib;
+       private BasicLibrary lib;
        private MetaData meta;
        private Story story;
        private TText textField;
@@ -28,14 +29,15 @@ public class TuiReaderStoryWindow extends TWindow {
        private List<TButton> navigationButtons;
        private TLabel chapterName;
 
-       public TuiReaderStoryWindow(TApplication app, Library lib, MetaData meta) {
+       public TuiReaderStoryWindow(TApplication app, BasicLibrary lib,
+                       MetaData meta) {
                this(app, lib, meta, 0);
        }
 
-       public TuiReaderStoryWindow(TApplication app, Library lib, MetaData meta,
-                       int chapter) {
+       public TuiReaderStoryWindow(TApplication app, BasicLibrary lib,
+                       MetaData meta, int chapter) {
                super(app, desc(meta), 0, 0, 60, 18, CENTERED | RESIZABLE);
-               
+
                this.lib = lib;
                this.meta = meta;
 
index 222d9d5285e562d735ae99a9806b0cf848ce0d86..f9d5299a575d7c7dc298ae1a631143b9c482d49b 100644 (file)
@@ -4,7 +4,8 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-import be.nikiroo.fanfix.Library;
+import be.nikiroo.fanfix.BasicLibrary;
+import be.nikiroo.fanfix.LocalLibrary;
 import be.nikiroo.fanfix.data.Chapter;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
@@ -15,7 +16,7 @@ import be.nikiroo.utils.test.TestCase;
 import be.nikiroo.utils.test.TestLauncher;
 
 public class LibraryTest extends TestLauncher {
-       private Library lib;
+       private BasicLibrary lib;
        private File tmp;
 
        public LibraryTest(String[] args) {
@@ -34,8 +35,7 @@ public class LibraryTest extends TestLauncher {
                                addTest(new TestCase("getList") {
                                        @Override
                                        public void test() throws Exception {
-                                               // TODO: getList
-                                               List<MetaData> metas = lib.getListBySource(null);
+                                               List<MetaData> metas = lib.getList();
                                                assertEquals(0, metas.size());
                                        }
                                });
@@ -46,8 +46,7 @@ public class LibraryTest extends TestLauncher {
                                                lib.save(story(luid1, "My story 1", source1, author1),
                                                                luid1, null);
 
-                                               // TODO: getList
-                                               List<MetaData> metas = lib.getListBySource(null);
+                                               List<MetaData> metas = lib.getList();
                                                assertEquals(1, metas.size());
                                        }
                                });
@@ -60,15 +59,13 @@ public class LibraryTest extends TestLauncher {
                                                lib.save(story(luid2, "My story 2", source2, author1),
                                                                luid2, null);
 
-                                               // TODO: getList
-                                               metas = lib.getListBySource(null);
+                                               metas = lib.getList();
                                                assertEquals(2, metas.size());
 
                                                lib.save(story(luid3, "My story 3", source2, author1),
                                                                luid3, null);
 
-                                               // TODO: getList
-                                               metas = lib.getListBySource(null);
+                                               metas = lib.getList();
                                                assertEquals(3, metas.size());
                                        }
                                });
@@ -77,11 +74,11 @@ public class LibraryTest extends TestLauncher {
                                        @Override
                                        public void test() throws Exception {
                                                // same luid as a previous one
-                                               lib.save(story(luid3, "My story 3", source2, author2),
-                                                               luid3, null);
+                                               lib.save(
+                                                               story(luid3, "My story 3 [edited]", source2,
+                                                                               author2), luid3, null);
 
-                                               // TODO: getList
-                                               List<MetaData> metas = lib.getListBySource(null);
+                                               List<MetaData> metas = lib.getList();
                                                assertEquals(3, metas.size());
                                        }
                                });
@@ -89,8 +86,7 @@ public class LibraryTest extends TestLauncher {
                                addTest(new TestCase("getList with results") {
                                        @Override
                                        public void test() throws Exception {
-                                               // TODO: getList
-                                               List<MetaData> metas = lib.getListBySource(null);
+                                               List<MetaData> metas = lib.getList();
                                                assertEquals(3, metas.size());
                                        }
                                });
@@ -132,7 +128,7 @@ public class LibraryTest extends TestLauncher {
                                        public void test() throws Exception {
                                                List<MetaData> metas = null;
 
-                                               lib.changeType(luid3, source1);
+                                               lib.changeSource(luid3, source1, null);
 
                                                metas = lib.getListBySource(source1);
                                                assertEquals(2, metas.size());
@@ -174,7 +170,7 @@ public class LibraryTest extends TestLauncher {
                tmp.delete();
                tmp.mkdir();
 
-               lib = new Library(tmp, OutputType.INFO_TEXT, OutputType.CBZ);
+               lib = new LocalLibrary(tmp, OutputType.INFO_TEXT, OutputType.CBZ);
        }
 
        @Override