UI update: smoother progress updates (still wip)
[fanfix.git] / src / be / nikiroo / fanfix / Library.java
index 68f93c5385c66ea52c7f4bd2fd6d43eb8ed8bb3b..69b497fd84f1730feda48aae6fab96b02a76d74c 100644 (file)
@@ -1,14 +1,17 @@
 package be.nikiroo.fanfix;
 
 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;
@@ -16,6 +19,8 @@ import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 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.
@@ -52,6 +57,74 @@ public class Library {
                dir.mkdirs();
        }
 
+       /**
+        * 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 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.
+        * 
+        * @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.
@@ -61,17 +134,16 @@ public class Library {
         * 
         * @return the stories
         */
-       public List<MetaData> getList(SupportType type) {
-               String typeString = type == null ? null : type.getSourceName();
-
+       public synchronized List<MetaData> getListByType(String type) {
                List<MetaData> list = new ArrayList<MetaData>();
-               for (Entry<MetaData, File> entry : getStories().entrySet()) {
+               for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
                        String storyType = entry.getValue().getParentFile().getName();
-                       if (typeString == null || typeString.equalsIgnoreCase(storyType)) {
+                       if (type == null || type.equalsIgnoreCase(storyType)) {
                                list.add(entry.getKey());
                        }
                }
 
+               Collections.sort(list);
                return list;
        }
 
@@ -83,9 +155,9 @@ public class Library {
         * 
         * @return the corresponding {@link Story}
         */
-       public MetaData getInfo(String luid) {
+       public synchronized MetaData getInfo(String luid) {
                if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
                                if (luid.equals(entry.getKey().getLuid())) {
                                        return entry.getKey();
                                }
@@ -103,9 +175,9 @@ public class Library {
         * 
         * @return the corresponding {@link Story}
         */
-       public File getFile(String luid) {
+       public synchronized File getFile(String luid) {
                if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
                                if (luid.equals(entry.getKey().getLuid())) {
                                        return entry.getValue();
                                }
@@ -120,19 +192,22 @@ public class Library {
         * 
         * @param luid
         *            the Library UID of the story
+        * @param pg
+        *            the optional progress reporter
         * 
-        * @return the corresponding {@link Story}
+        * @return the corresponding {@link Story} or NULL if not found
         */
-       public Story getStory(String luid) {
+       public synchronized Story getStory(String luid, Progress pg) {
                if (luid != null) {
-                       for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       for (Entry<MetaData, File> entry : getStories(null).entrySet()) {
                                if (luid.equals(entry.getKey().getLuid())) {
                                        try {
                                                SupportType type = SupportType.valueOfAllOkUC(entry
                                                                .getKey().getType());
                                                URL url = entry.getValue().toURI().toURL();
                                                if (type != null) {
-                                                       return BasicSupport.getSupport(type).process(url);
+                                                       return BasicSupport.getSupport(type).process(url,
+                                                                       pg);
                                                } else {
                                                        throw new IOException("Unknown type: "
                                                                        + entry.getKey().getType());
@@ -148,6 +223,11 @@ public class Library {
                        }
                }
 
+               if (pg != null) {
+                       pg.setMinMax(0, 1);
+                       pg.setProgress(1);
+               }
+
                return null;
        }
 
@@ -157,19 +237,21 @@ public class 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) throws IOException {
+       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), null);
+               return save(support.process(url, pg), null);
        }
 
        /**
@@ -181,20 +263,35 @@ public class Library {
         *            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)
+       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);
                }
 
-               return out.process(getStory(luid), target);
+               Story story = getStory(luid, pgGetStory);
+               if (story == null) {
+                       throw new IOException("Cannot find story to export: " + luid);
+               }
+
+               return out.process(story, target, pgOut);
        }
 
        /**
@@ -202,14 +299,16 @@ public class 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) throws IOException {
-               return save(story, null);
+       public Story save(Story story, Progress pg) throws IOException {
+               return save(story, null, pg);
        }
 
        /**
@@ -220,19 +319,22 @@ public class Library {
         *            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
         */
-       private Story save(Story story, String luid) throws IOException {
+       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(); // refresh lastId if needed
+                       getStories(null); // refresh lastId if needed
                        key.setLuid(String.format("%03d", (++lastId)));
                } else {
                        key.setLuid(luid);
@@ -251,12 +353,71 @@ public class Library {
                }
 
                BasicOutput it = BasicOutput.getOutput(out, true);
-               File file = it.process(story, getFile(key).getPath());
-               getStories().put(story.getMeta(), file);
+               it.process(story, getFile(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;
+
+               MetaData meta = getInfo(luid);
+               File file = getStories(null).get(meta);
+
+               if (file != null) {
+                       if (file.delete()) {
+                               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);
+                                       IOUtils.deltree(file);
+                               }
+
+                               File infoFile = new File(path + ".info");
+                               if (!infoFile.exists()) {
+                                       infoFile = new File(path.substring(0, path.length()
+                                                       - fileExt.length())
+                                                       + ".info");
+                               }
+                               infoFile.delete();
+
+                               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()));
+                               }
+                               coverFile.delete();
+
+                               ok = true;
+                       }
+
+                       // clear cache
+                       stories.clear();
+               }
+
+               return ok;
+       }
+
        /**
         * The directory (full path) where the {@link Story} related to this
         * {@link MetaData} should be located on disk.
@@ -281,63 +442,104 @@ public class Library {
         * @return the target
         */
        private File getFile(MetaData key) {
-               String title = key.getTitle().replaceAll("[^a-zA-Z0-9._+-]", "_");
+               String title = key.getTitle();
+               if (title == null) {
+                       title = "";
+               }
+               title = title.replaceAll("[^a-zA-Z0-9._+-]", "_");
                return new File(getDir(key), key.getLuid() + "_" + title);
        }
 
        /**
         * Return all the known stories in this {@link Library} object.
         * 
+        * @param pg
+        *            the optional progress reporter
+        * 
         * @return the stories
         */
-       private Map<MetaData, File> getStories() {
+       private synchronized Map<MetaData, File> getStories(Progress pg) {
+               if (pg == null) {
+                       pg = new Progress();
+               } else {
+                       pg.setMinMax(0, 100);
+               }
+
                if (stories.isEmpty()) {
                        lastId = 0;
 
-                       String ext = ".info";
-                       for (File dir : baseDir.listFiles()) {
-                               if (dir.isDirectory()) {
-                                       for (File file : dir.listFiles()) {
+                       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);
+
+                       final String ext = ".info";
+                       for (File dir : dirs) {
+                               File[] files = dir.listFiles(new FileFilter() {
+                                       public boolean accept(File file) {
+                                               return file != null
+                                                               && file.getPath().toLowerCase().endsWith(ext);
+                                       }
+                               });
+
+                               Progress pgFiles = new Progress(0, files.length);
+                               pgDirs.addProgress(pgFiles, 100);
+                               pgDirs.setName("Loading from: " + dir.getName());
+
+                               for (File file : files) {
+                                       try {
+                                               pgFiles.setName(file.getName());
+                                               MetaData meta = InfoReader.readMeta(file);
                                                try {
-                                                       if (file.getPath().toLowerCase().endsWith(ext)) {
-                                                               MetaData meta = InfoReader.readMeta(file);
-                                                               try {
-                                                                       int id = Integer.parseInt(meta.getLuid());
-                                                                       if (id > lastId) {
-                                                                               lastId = id;
-                                                                       }
-
-                                                                       // Replace .info with whatever is needed:
-                                                                       String path = file.getPath();
-                                                                       path = path.substring(0, path.length()
-                                                                                       - ext.length());
-
-                                                                       String newExt = getOutputType(meta)
-                                                                                       .getDefaultExtension();
-
-                                                                       file = new File(path + newExt);
-                                                                       //
-
-                                                                       stories.put(meta, file);
-
-                                                               } catch (Exception e) {
-                                                                       // not normal!!
-                                                                       Instance.syserr(new IOException(
-                                                                                       "Cannot understand the LUID of "
-                                                                                                       + file.getPath() + ": "
-                                                                                                       + meta.getLuid(), e));
-                                                               }
+                                                       int id = Integer.parseInt(meta.getLuid());
+                                                       if (id > lastId) {
+                                                               lastId = id;
                                                        }
-                                               } catch (IOException e) {
-                                                       // We should not have not-supported files in the
-                                                       // library
+
+                                                       // Replace .info with whatever is needed:
+                                                       String path = file.getPath();
+                                                       path = path.substring(0,
+                                                                       path.length() - ext.length());
+
+                                                       String newExt = getOutputType(meta)
+                                                                       .getDefaultExtension(true);
+
+                                                       file = new File(path + newExt);
+                                                       //
+
+                                                       stories.put(meta, file);
+
+                                               } catch (Exception e) {
+                                                       // not normal!!
                                                        Instance.syserr(new IOException(
-                                                                       "Cannot load file from library: "
-                                                                                       + file.getPath(), e));
+                                                                       "Cannot understand the LUID of "
+                                                                                       + file.getPath() + ": "
+                                                                                       + 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: "
+                                                                               + file.getPath(), e));
+                                       } finally {
+                                               pgFiles.setProgress(pgFiles.getProgress() + 1);
+
+                                               System.out.println("files: " + pgFiles.getProgress()
+                                                               + "/" + pgFiles.getMax());
+                                               System.out.println("dirs : " + pgDirs.getProgress()
+                                                               + "/" + pgDirs.getMax());
                                        }
                                }
+
+                               pgFiles.setName(null);
                        }
+
+                       pgDirs.setName("Loading directories");
                }
 
                return stories;