X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Flibrary%2FLocalLibrary.java;h=25f2ec960627ab354295289f73a7c93d6bbbe3ed;hb=cfdaf6052ddc5ca44cf19f1f6d9f154cc8443024;hp=5e0ad40097f8add91f78d73cf1cf61b3334c53e7;hpb=9fe3f17729759e933d7687cc2838f7255f2c9283;p=nikiroo-utils.git diff --git a/src/be/nikiroo/fanfix/library/LocalLibrary.java b/src/be/nikiroo/fanfix/library/LocalLibrary.java index 5e0ad40..25f2ec9 100644 --- a/src/be/nikiroo/fanfix/library/LocalLibrary.java +++ b/src/be/nikiroo/fanfix/library/LocalLibrary.java @@ -3,6 +3,7 @@ package be.nikiroo.fanfix.library; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -12,15 +13,16 @@ import java.util.Map; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.bundles.Config; +import be.nikiroo.fanfix.bundles.ConfigBundle; 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.HashUtils; import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.Image; -import be.nikiroo.utils.MarkableFileInputStream; import be.nikiroo.utils.Progress; /** @@ -30,8 +32,10 @@ import be.nikiroo.utils.Progress; */ public class LocalLibrary extends BasicLibrary { private int lastId; + private Object lock = new Object(); private Map stories; // Files: [ infoFile, TargetFile ] private Map sourceCovers; + private Map authorCovers; private File baseDir; private OutputType text; @@ -42,11 +46,15 @@ public class LocalLibrary extends BasicLibrary { * * @param baseDir * the directory where to find the {@link Story} objects + * @param config + * the configuration used to know which kind of default + * {@link OutputType} to use for images and non-images stories */ - public LocalLibrary(File baseDir) { - this(baseDir, Instance.getConfig().getString( - Config.NON_IMAGES_DOCUMENT_TYPE), Instance.getConfig() - .getString(Config.IMAGES_DOCUMENT_TYPE), false); + public LocalLibrary(File baseDir, ConfigBundle config) { + this(baseDir, // + config.getString(Config.FILE_FORMAT_NON_IMAGES_DOCUMENT_TYPE), + config.getString(Config.FILE_FORMAT_IMAGES_DOCUMENT_TYPE), + false); } /** @@ -64,8 +72,9 @@ public class LocalLibrary extends BasicLibrary { */ public LocalLibrary(File baseDir, String text, String image, boolean defaultIsHtml) { - this(baseDir, OutputType.valueOfAllOkUC(text, - defaultIsHtml ? OutputType.HTML : OutputType.INFO_TEXT), + this(baseDir, + OutputType.valueOfAllOkUC(text, + defaultIsHtml ? OutputType.HTML : OutputType.INFO_TEXT), OutputType.valueOfAllOkUC(image, defaultIsHtml ? OutputType.HTML : OutputType.CBZ)); } @@ -87,7 +96,7 @@ public class LocalLibrary extends BasicLibrary { this.lastId = 0; this.stories = null; - this.sourceCovers = new HashMap(); + this.sourceCovers = null; baseDir.mkdirs(); } @@ -98,19 +107,37 @@ public class LocalLibrary extends BasicLibrary { } @Override - public File getFile(String luid, Progress pg) { - File[] files = getStories(pg).get(getInfo(luid)); - if (files != null) { - return files[1]; + public File getFile(String luid, Progress pg) throws IOException { + Instance.getInstance().getTraceHandler().trace( + this.getClass().getSimpleName() + ": get file for " + luid); + + File file = null; + String mess = "no file found for "; + + MetaData meta = getInfo(luid); + if (meta != null) { + File[] files = getStories(pg).get(meta); + if (files != null) { + mess = "file retrieved for "; + file = files[1]; + } } - return null; + Instance.getInstance().getTraceHandler() + .trace(this.getClass().getSimpleName() + ": " + mess + luid + + " (" + meta.getTitle() + ")"); + + return file; } @Override - public Image getCover(String luid) { + public Image getCover(String luid) throws IOException { MetaData meta = getInfo(luid); if (meta != null) { + if (meta.getCover() != null) { + return meta.getCover(); + } + File[] files = getStories(null).get(meta); if (files != null) { File infoFile = files[0]; @@ -119,7 +146,7 @@ public class LocalLibrary extends BasicLibrary { meta = InfoReader.readMeta(infoFile, true); return meta.getCover(); } catch (IOException e) { - Instance.getTraceHandler().error(e); + Instance.getInstance().getTraceHandler().error(e); } } } @@ -128,15 +155,25 @@ public class LocalLibrary extends BasicLibrary { } @Override - protected synchronized void invalidateInfo(String luid) { - stories = null; - sourceCovers = new HashMap(); + protected void updateInfo(MetaData meta) { + invalidateInfo(); + } + + @Override + protected void invalidateInfo(String luid) { + synchronized (lock) { + stories = null; + sourceCovers = null; + } } @Override - protected synchronized int getNextId() { + protected String getNextId() { getStories(null); // make sure lastId is set - return ++lastId; + + synchronized (lock) { + return String.format("%03d", ++lastId); + } } @Override @@ -166,7 +203,7 @@ public class LocalLibrary extends BasicLibrary { throws IOException { File newDir = getExpectedDir(meta.getSource()); if (!newDir.exists()) { - newDir.mkdir(); + newDir.mkdirs(); } List relatedFiles = getRelatedFiles(meta.getLuid()); @@ -176,13 +213,13 @@ public class LocalLibrary extends BasicLibrary { // 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); + String name = relatedFile.getName().replaceFirst("\\.info$", + ""); relatedFile.delete(); + InfoCover.writeInfo(newDir, name, meta); relatedFile.getParentFile().delete(); } catch (IOException e) { - Instance.getTraceHandler().error(e); + Instance.getInstance().getTraceHandler().error(e); } } else { relatedFile.renameTo(new File(newDir, relatedFile.getName())); @@ -190,28 +227,166 @@ public class LocalLibrary extends BasicLibrary { } } - invalidateInfo(); + updateInfo(meta); } @Override - public Image getSourceCover(String source) { - if (!sourceCovers.containsKey(source)) { - sourceCovers.put(source, super.getSourceCover(source)); + public Image getCustomSourceCover(String source) { + synchronized (lock) { + if (sourceCovers == null) { + sourceCovers = new HashMap(); + } + } + + synchronized (lock) { + Image img = sourceCovers.get(source); + if (img != null) { + return img; + } + } + + File coverDir = getExpectedDir(source); + if (coverDir.isDirectory()) { + File cover = new File(coverDir, ".cover.png"); + if (cover.exists()) { + InputStream in; + try { + in = new FileInputStream(cover); + try { + synchronized (lock) { + Image img = new Image(in); + if (img.getSize() == 0) { + img.close(); + throw new IOException( + "Empty image not accepted"); + } + sourceCovers.put(source, img); + } + } finally { + in.close(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException( + "Cannot load the existing custom source cover: " + + cover, + e)); + } + } + } + + synchronized (lock) { + return sourceCovers.get(source); + } + } + + @Override + public Image getCustomAuthorCover(String author) { + synchronized (lock) { + if (authorCovers == null) { + authorCovers = new HashMap(); + } + } + + synchronized (lock) { + Image img = authorCovers.get(author); + if (img != null) { + return img; + } } - return sourceCovers.get(source); + File cover = getAuthorCoverFile(author); + if (cover.exists()) { + InputStream in; + try { + in = new FileInputStream(cover); + try { + synchronized (lock) { + Image img = new Image(in); + if (img.getSize() == 0) { + img.close(); + throw new IOException( + "Empty image not accepted"); + } + authorCovers.put(author, img); + } + } finally { + in.close(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + Instance.getInstance().getTraceHandler() + .error(new IOException( + "Cannot load the existing custom author cover: " + + cover, + e)); + } + } + + synchronized (lock) { + return authorCovers.get(author); + } + } + + @Override + public void setSourceCover(String source, String luid) throws IOException { + setSourceCover(source, getCover(luid)); } @Override - public void setSourceCover(String source, String luid) { - sourceCovers.put(source, getCover(luid)); - File cover = new File(getExpectedDir(source), ".cover.png"); + public void setAuthorCover(String author, String luid) throws IOException { + setAuthorCover(author, getCover(luid)); + } + + /** + * Set the source cover to the given story cover. + * + * @param source + * the source to change + * @param coverImage + * the cover image + */ + void setSourceCover(String source, Image coverImage) { + File dir = getExpectedDir(source); + dir.mkdirs(); + File cover = new File(dir, ".cover"); try { - Instance.getCache().saveAsImage(sourceCovers.get(source), cover, + Instance.getInstance().getCache().saveAsImage(coverImage, cover, true); + synchronized (lock) { + if (sourceCovers != null) { + sourceCovers.put(source, coverImage); + } + } } catch (IOException e) { - Instance.getTraceHandler().error(e); - sourceCovers.remove(source); + Instance.getInstance().getTraceHandler().error(e); + } + } + + /** + * Set the author cover to the given story cover. + * + * @param author + * the author to change + * @param coverImage + * the cover image + */ + void setAuthorCover(String author, Image coverImage) { + File cover = getAuthorCoverFile(author); + cover.getParentFile().mkdirs(); + try { + Instance.getInstance().getCache().saveAsImage(coverImage, cover, + true); + synchronized (lock) { + if (authorCovers != null) { + authorCovers.put(author, coverImage); + } + } + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); } } @@ -232,19 +407,21 @@ public class LocalLibrary extends BasicLibrary { if (meta != null && meta.getType().equals(expectedType)) { File from = otherLocalLibrary.getExpectedDir(meta.getSource()); File to = this.getExpectedDir(meta.getSource()); - List sources = otherLocalLibrary.getRelatedFiles(luid); - if (!sources.isEmpty()) { - pg.setMinMax(0, sources.size()); + List relatedFiles = otherLocalLibrary + .getRelatedFiles(luid); + if (!relatedFiles.isEmpty()) { + pg.setMinMax(0, relatedFiles.size()); } - for (File source : sources) { - File target = new File(source.getAbsolutePath().replace( - from.getAbsolutePath(), to.getAbsolutePath())); - if (!source.equals(target)) { + for (File relatedFile : relatedFiles) { + File target = new File(relatedFile.getAbsolutePath() + .replace(from.getAbsolutePath(), + to.getAbsolutePath())); + if (!relatedFile.equals(target)) { target.getParentFile().mkdirs(); InputStream in = null; try { - in = new FileInputStream(source); + in = new FileInputStream(relatedFile); IOUtils.write(in, target); } catch (IOException e) { if (in != null) { @@ -269,8 +446,6 @@ public class LocalLibrary extends BasicLibrary { } super.imprt(other, luid, pg); - - invalidateInfo(); } /** @@ -289,6 +464,22 @@ public class LocalLibrary extends BasicLibrary { return text; } + /** + * Return the default {@link OutputType} for this kind of {@link Story}. + * + * @param imageDocument + * TRUE for images document, FALSE for text documents + * + * @return the type + */ + public String getOutputType(boolean imageDocument) { + if (imageDocument) { + return image.toString(); + } + + return text.toString(); + } + /** * Get the target {@link File} related to the given .info * {@link File} and {@link MetaData}. @@ -324,8 +515,11 @@ public class LocalLibrary extends BasicLibrary { title = ""; } title = title.replaceAll("[^a-zA-Z0-9._+-]", "_"); - return new File(getExpectedDir(key.getSource()), key.getLuid() + "_" - + title); + if (title.length() > 40) { + title = title.substring(0, 40); + } + return new File(getExpectedDir(key.getSource()), + key.getLuid() + "_" + title); } /** @@ -338,10 +532,45 @@ public class LocalLibrary extends BasicLibrary { * @return the target directory */ private File getExpectedDir(String source) { - String sanitizedSource = source.replaceAll("[^a-zA-Z0-9._+-]", "_"); + String sanitizedSource = source.replaceAll("[^a-zA-Z0-9._+/-]", "_"); + + while (sanitizedSource.startsWith("/") + || sanitizedSource.startsWith("_")) { + if (sanitizedSource.length() > 1) { + sanitizedSource = sanitizedSource.substring(1); + } else { + sanitizedSource = ""; + } + } + + sanitizedSource = sanitizedSource.replace("/", File.separator); + + if (sanitizedSource.isEmpty()) { + sanitizedSource = "_EMPTY"; + } + return new File(baseDir, sanitizedSource); } + /** + * Return the full path to the file to use for the custom cover of this + * author. + *

+ * One or more of the parent directories MAY not exist. + * + * @param author + * the author + * + * @return the custom cover file + */ + private File getAuthorCoverFile(String author) { + File aDir = new File(baseDir, "_AUTHORS"); + String hash = HashUtils.md5(author); + String ext = Instance.getInstance().getConfig() + .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER); + return new File(aDir, hash + "." + ext.toLowerCase()); + } + /** * Return the list of files/directories on disk for this {@link Story}. *

@@ -383,20 +612,31 @@ public class LocalLibrary extends BasicLibrary { } } - String coverExt = "." - + Instance.getConfig().getString(Config.IMAGE_FORMAT_COVER) - .toLowerCase(); + String coverExt = "." + Instance.getInstance().getConfig() + .getString(Config.FILE_FORMAT_IMAGE_FORMAT_COVER).toLowerCase(); File coverFile = new File(path + coverExt); if (!coverFile.exists()) { - coverFile = new File(path.substring(0, - path.length() - fileExt.length()) - + coverExt); + coverFile = new File( + path.substring(0, path.length() - fileExt.length()) + + coverExt); } if (coverFile.exists()) { files.add(coverFile); } + String summaryExt = ".summary"; + File summaryFile = new File(path + summaryExt); + if (!summaryFile.exists()) { + summaryFile = new File( + path.substring(0, path.length() - fileExt.length()) + + summaryExt); + } + + if (summaryFile.exists()) { + files.add(summaryFile); + } + return files; } @@ -410,122 +650,129 @@ public class LocalLibrary extends BasicLibrary { * @param pg * the optional {@link Progress} * - * @return the list of stories + * @return the list of stories (for each item, the first {@link File} is the + * info file, the second file is the target {@link File}) */ - private synchronized Map getStories(Progress pg) { + private Map getStories(Progress pg) { if (pg == null) { pg = new Progress(); } else { pg.setMinMax(0, 100); } + Map stories = this.stories; if (stories == null) { - stories = new HashMap(); + stories = getStoriesDo(pg); + synchronized (lock) { + if (this.stories == null) + this.stories = stories; + else + stories = this.stories; + } + } - lastId = 0; + pg.done(); + return stories; - File[] dirs = baseDir.listFiles(new FileFilter() { - @Override - public boolean accept(File file) { - return file != null && file.isDirectory(); - } - }); - - if (dirs != null) { - Progress pgDirs = new Progress(0, 100 * dirs.length); - pg.addProgress(pgDirs, 100); - - for (File dir : dirs) { - File[] infoFiles = dir.listFiles(new FileFilter() { - @Override - 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()); + /** + * Actually do the work of {@link LocalLibrary#getStories(Progress)} (i.e., + * do not retrieve the cache). + * + * @param pg + * the optional {@link Progress} + * + * @return the list of stories (for each item, the first {@link File} is the + * info file, the second file is the target {@link File}) + */ + private synchronized Map getStoriesDo(Progress pg) { + if (pg == null) { + pg = new Progress(); + } else { + pg.setMinMax(0, 100); + } - String source = null; - for (File infoFile : infoFiles) { - pgFiles.setName(infoFile.getName()); - try { - MetaData meta = InfoReader - .readMeta(infoFile, false); - source = meta.getSource(); - try { - int id = Integer.parseInt(meta.getLuid()); - if (id > lastId) { - lastId = id; - } + Map stories = new HashMap(); - 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.getTraceHandler().error( - new IOException( - "Cannot load file from library: " - + infoFile, e)); - } - pgFiles.add(1); - } + File[] dirs = baseDir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file != null && file.isDirectory(); + } + }); - File cover = new File(dir, ".cover.png"); - if (cover.exists()) { - try { - InputStream in = new FileInputStream(cover); - try { - sourceCovers.put(source, new Image(in)); - } finally { - in.close(); - } - } catch (IOException e) { - Instance.getTraceHandler().error(e); - } - } + if (dirs != null) { + Progress pgDirs = new Progress(0, 100 * dirs.length); + pg.addProgress(pgDirs, 100); - pgFiles.setName(null); - } + for (File dir : dirs) { + Progress pgFiles = new Progress(); + pgDirs.addProgress(pgFiles, 100); + pgDirs.setName("Loading from: " + dir.getName()); - pgDirs.setName("Loading directories"); + addToStories(stories, pgFiles, dir); + + pgFiles.setName(null); } + + pgDirs.setName("Loading directories"); } pg.done(); + return stories; } - /** - * Fix the source cover to the given story cover. - * - * @param source - * the source to change - * @param coverImage - * the cover image - */ - void setSourceCover(String source, Image coverImage) { - sourceCovers.put(source, coverImage); - File cover = new File(getExpectedDir(source), ".cover.png"); - try { - Instance.getCache().saveAsImage(sourceCovers.get(source), cover, - true); - } catch (IOException e) { - Instance.getTraceHandler().error(e); - sourceCovers.remove(source); + private void addToStories(Map stories, Progress pgFiles, + File dir) { + File[] infoFilesAndSubdirs = dir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + boolean info = file != null && file.isFile() + && file.getPath().toLowerCase().endsWith(".info"); + boolean dir = file != null && file.isDirectory(); + boolean isExpandedHtml = new File(file, "index.html").isFile(); + return info || (dir && !isExpandedHtml); + } + }); + + if (pgFiles != null) { + pgFiles.setMinMax(0, infoFilesAndSubdirs.length); + } + + for (File infoFileOrSubdir : infoFilesAndSubdirs) { + if (infoFileOrSubdir.isDirectory()) { + addToStories(stories, null, infoFileOrSubdir); + } else { + try { + MetaData meta = InfoReader.readMeta(infoFileOrSubdir, + false); + try { + int id = Integer.parseInt(meta.getLuid()); + if (id > lastId) { + lastId = id; + } + + stories.put(meta, new File[] { infoFileOrSubdir, + getTargetFile(meta, infoFileOrSubdir) }); + } catch (Exception e) { + // not normal!! + throw new IOException("Cannot understand the LUID of " + + infoFileOrSubdir + ": " + meta.getLuid(), e); + } + } catch (IOException e) { + // We should not have not-supported files in the + // library + Instance.getInstance().getTraceHandler().error( + new IOException("Cannot load file from library: " + + infoFileOrSubdir, e)); + } + } + + if (pgFiles != null) { + pgFiles.add(1); + } } } }