X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2FLibrary.java;h=f1f7121385faeab1bae5ba686dbdcf2fce993ae5;hp=0d9e067f30dcb6c24fe2e3a1e48fa098464f04e9;hb=70c9b112926f1cf95b2fddd0bb504ab37d6ddd1e;hpb=b4dc6ab518ded2dd92e4cbb02ac615b1d57e8e6d diff --git a/src/be/nikiroo/fanfix/Library.java b/src/be/nikiroo/fanfix/Library.java index 0d9e067..f1f7121 100644 --- a/src/be/nikiroo/fanfix/Library.java +++ b/src/be/nikiroo/fanfix/Library.java @@ -1,27 +1,37 @@ 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. *

* 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}. + *

+ * Most of the {@link Library} functions work on either the LUID or a partial + * (cover not included) {@link MetaData} object. * * @author niki */ @@ -53,54 +63,116 @@ public class Library { } /** - * List all the known types of stories. + * 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 List getTypes() { + public synchronized List getTypes() { List list = new ArrayList(); - for (Entry entry : getStories().entrySet()) { - String storyType = entry.getValue().getParentFile().getName(); + for (Entry 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 getAuthors() { + List list = new ArrayList(); + for (Entry 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. + *

+ * Cover images not included. + * + * @param author + * the author of the stories to retrieve, or NULL for all + * + * @return the stories + */ + public synchronized List getListByAuthor(String author) { + List list = new ArrayList(); + for (Entry 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. + *

+ * Cover images not included. * * @param type * the type of story to retrieve, or NULL for all * * @return the stories */ - public List getList(String type) { + public synchronized List getListByType(String type) { + if (type != null) { + // convert the type to dir name + type = getDir(type).getName(); + } + List list = new ArrayList(); - for (Entry entry : getStories().entrySet()) { + for (Entry entry : getStories(null).entrySet()) { String storyType = entry.getValue().getParentFile().getName(); if (type == null || type.equalsIgnoreCase(storyType)) { list.add(entry.getKey()); } } + Collections.sort(list); return list; } /** - * Retrieve a {@link File} corresponding to the given {@link Story}. + * Retrieve a {@link File} corresponding to the given {@link Story}, cover + * image not included. * * @param luid * the Library UID of the story * * @return the corresponding {@link Story} */ - public MetaData getInfo(String luid) { + public synchronized MetaData getInfo(String luid) { if (luid != null) { - for (Entry entry : getStories().entrySet()) { + for (Entry entry : getStories(null).entrySet()) { if (luid.equals(entry.getKey().getLuid())) { return entry.getKey(); } @@ -118,9 +190,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 entry : getStories().entrySet()) { + for (Entry entry : getStories(null).entrySet()) { if (luid.equals(entry.getKey().getLuid())) { return entry.getValue(); } @@ -130,24 +202,50 @@ public class Library { 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) { + try { + File infoFile = new File(getFile(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 Story getStory(String luid) { + public synchronized Story getStory(String luid, Progress pg) { if (luid != null) { - for (Entry entry : getStories().entrySet()) { + for (Entry 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()); @@ -163,6 +261,11 @@ public class Library { } } + if (pg != null) { + pg.setMinMax(0, 1); + pg.setProgress(1); + } + return null; } @@ -172,19 +275,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); } /** @@ -196,25 +301,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); } - Story story = getStory(luid); + Story story = getStory(luid, pgGetStory); if (story == null) { throw new IOException("Cannot find story to export: " + luid); } - return out.process(getStory(luid), target); + return out.process(story, target, pgOut); } /** @@ -222,14 +337,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); } /** @@ -240,26 +357,29 @@ public class Library { * the {@link Story} to save * @param luid * the correct 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 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); } - getDir(key).mkdirs(); - if (!getDir(key).exists()) { + getDir(key.getSource()).mkdirs(); + if (!getDir(key.getSource()).exists()) { throw new IOException("Cannot create library dir"); } @@ -271,23 +391,156 @@ 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; + + List 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 newSourcethe + * new source + * + * @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 = getDir(meta.getSource()); + if (!newDir.exists()) { + newDir.mkdir(); + } + + List 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; + } + + /** + * Return the list of files/dirs on disk for this {@link Story}. + *

+ * 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 getFiles(String luid) { + List files = new ArrayList(); + + MetaData meta = getInfo(luid); + File file = getStories(null).get(meta); + + 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 key - * the {@link Story} {@link MetaData} + * @param type + * the type (source) * * @return the target directory */ - private File getDir(MetaData key) { - String source = key.getSource().replaceAll("[^a-zA-Z0-9._+-]", "_"); + private File getDir(String type) { + String source = type.replaceAll("[^a-zA-Z0-9._+-]", "_"); return new File(baseDir, source); } @@ -301,68 +554,120 @@ public class Library { * @return the target */ private File getFile(MetaData key) { - String title = key.getTitle().replaceAll("[^a-zA-Z0-9._+-]", "_"); - return new File(getDir(key), key.getLuid() + "_" + title); + String title = key.getTitle(); + if (title == null) { + title = ""; + } + title = title.replaceAll("[^a-zA-Z0-9._+-]", "_"); + return new File(getDir(key.getSource()), key.getLuid() + "_" + title); } /** * Return all the known stories in this {@link Library} object. * + * @param pg + * the optional progress reporter + * * @return the stories */ - private Map getStories() { + private synchronized Map 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); + + 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 entry = readMeta(file, false); 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(entry.getKey().getLuid()); + if (id > lastId) { + lastId = id; } - } 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)); + + 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 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() { + 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}. *