# Fanfix
+## Version wip
+- Library: perf improvement when retrieving the stories (cover not loaded when not needed)
+- UI: perf improvement when displaying books (cover resized then cached)
+
## Version 1.4.2
- New Options menu in UI to configure the program (minimalist for now)
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
* in case of I/O error
*/
public File addToCache(InputStream in, String uniqueID) throws IOException {
- File file = getCached(new File(uniqueID).toURI().toURL());
+ File file = getCached(uniqueID);
IOUtils.write(in, file);
return file;
}
+ /**
+ * Return the {@link InputStream} corresponding to the given unique ID, or
+ * NULL if none found.
+ *
+ * @param uniqueID
+ * the unique ID
+ *
+ * @return the content or NULL
+ */
+ public InputStream getFromCache(String uniqueID) {
+ File file = getCached(uniqueID);
+ if (file.exists()) {
+ try {
+ return new MarkableFileInputStream(new FileInputStream(file));
+ } catch (FileNotFoundException e) {
+ }
+ }
+
+ return null;
+ }
+
/**
* Clean the cache (delete the cached items).
*
*/
private File getCached(URL url) {
String name = url.getHost();
- if (name == null || name.length() == 0) {
+ if (name == null || name.isEmpty()) {
name = url.getFile();
} else {
name = url.toString();
}
- name = name.replace('/', '_').replace(':', '_');
+ return getCached(name);
+ }
+
+ /**
+ * Get the cache resource from the cache if it is present for this unique
+ * ID.
+ *
+ * @param url
+ * the url
+ * @return the cached version if present, NULL if not
+ */
+ private File getCached(String uniqueID) {
+ uniqueID = uniqueID.replace('/', '_').replace(':', '_')
+ .replace("\\", "_");
- return new File(dir, name);
+ return new File(dir, uniqueID);
}
/**
package be.nikiroo.fanfix;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
* <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
*/
/**
* 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
/**
* 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
}
/**
- * 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 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}.
*
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);
+ && file.getPath().toLowerCase()
+ .endsWith(".info");
}
});
pgDirs.setName("Loading from: " + dir.getName());
for (File file : files) {
+ pgFiles.setName(file.getName());
try {
- pgFiles.setName(file.getName());
- MetaData meta = InfoReader.readMeta(file);
+ Entry<MetaData, File> entry = readMeta(file, false);
try {
- int id = Integer.parseInt(meta.getLuid());
+ int id = Integer.parseInt(entry.getKey().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(true);
-
- file = new File(path + newExt);
- //
-
- stories.put(meta, file);
-
+ stories.put(entry.getKey(), entry.getValue());
} catch (Exception e) {
// not normal!!
- Instance.syserr(new IOException(
+ throw new IOException(
"Cannot understand the LUID of "
+ file.getPath() + ": "
- + meta.getLuid(), e));
+ + entry.getKey().getLuid(), e);
}
} catch (IOException e) {
// We should not have not-supported files in the
Instance.syserr(new IOException(
"Cannot load file from library: "
+ file.getPath(), e));
- } finally {
- pgFiles.add(1);
}
+ pgFiles.add(1);
}
pgFiles.setName(null);
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}.
*
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.EventListener;
import java.util.List;
+import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import be.nikiroo.fanfix.Instance;
import be.nikiroo.fanfix.data.MetaData;
import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.utils.IOUtils;
import be.nikiroo.utils.ui.UIUtils;
/**
optSecondary = "(" + optSecondary + ")";
}
- icon = new JLabel(generateCoverIcon(meta.getCover()));
-
+ icon = new JLabel(generateCoverIcon(meta));
title = new JLabel(
String.format(
"<html>"
}
/**
- * Generate a cover icon based upon the given cover image (which may be
- * NULL).
+ * Generate a cover icon based upon the given {@link MetaData}.
*
- * @param image
- * the cover image, or NULL for none
+ * @param meta
+ * the {@link MetaData} about the target {@link Story}
*
* @return the icon
*/
- private ImageIcon generateCoverIcon(BufferedImage image) {
- BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH
- + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET,
- BufferedImage.TYPE_4BYTE_ABGR);
- Graphics2D g = resizedImage.createGraphics();
- g.setColor(Color.white);
- g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
- if (image != null) {
- g.drawImage(image, 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT, null);
- } else {
- g.setColor(Color.black);
- g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET + COVER_HEIGHT);
- g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET + COVER_HEIGHT);
+ private ImageIcon generateCoverIcon(MetaData meta) {
+ String id = meta.getUuid() + ".thumb_" + SPINE_WIDTH + "x"
+ + COVER_WIDTH + "+" + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@"
+ + HOFFSET;
+ BufferedImage resizedImage = null;
+
+ InputStream in = Instance.getCache().getFromCache(id);
+ if (in != null) {
+ try {
+ resizedImage = IOUtils.toImage(in);
+ in.close();
+ in = null;
+ } catch (IOException e) {
+ Instance.syserr(e);
+ }
+ }
+
+ if (resizedImage == null) {
+ try {
+ BufferedImage cover = Instance.getLibrary().getCover(
+ meta.getLuid());
+
+ resizedImage = new BufferedImage(SPINE_WIDTH + COVER_WIDTH,
+ SPINE_HEIGHT + COVER_HEIGHT + HOFFSET,
+ BufferedImage.TYPE_4BYTE_ABGR);
+ Graphics2D g = resizedImage.createGraphics();
+ g.setColor(Color.white);
+ g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
+ if (cover != null) {
+ g.drawImage(cover, 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT,
+ null);
+ } else {
+ g.setColor(Color.black);
+ g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET + COVER_HEIGHT);
+ g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET + COVER_HEIGHT);
+ }
+ g.dispose();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ImageIO.write(resizedImage, "png", out);
+ byte[] imageBytes = out.toByteArray();
+ in = new ByteArrayInputStream(imageBytes);
+ Instance.getCache().addToCache(in, id);
+ in.close();
+ in = null;
+ } catch (MalformedURLException e) {
+ Instance.syserr(e);
+ } catch (IOException e) {
+ Instance.syserr(e);
+ }
}
- g.dispose();
return new ImageIcon(resizedImage);
}
}
if (tmpInfo.exists()) {
- meta = InfoReader.readMeta(tmpInfo);
+ meta = InfoReader.readMeta(tmpInfo, true);
if (cover != null) {
meta.setCover(cover);
}
// not complete: no "description" tag
public class InfoReader {
- public static MetaData readMeta(File infoFile) throws IOException {
+ public static MetaData readMeta(File infoFile, boolean withCover)
+ throws IOException {
if (infoFile == null) {
throw new IOException("File is null");
}
InputStream in = new MarkableFileInputStream(new FileInputStream(
infoFile));
try {
- return createMeta(infoFile.toURI().toURL(), in);
+ return createMeta(infoFile.toURI().toURL(), in, withCover);
} finally {
in.close();
in = null;
}
}
- private static MetaData createMeta(URL sourceInfoFile, InputStream in)
- throws IOException {
+ private static MetaData createMeta(URL sourceInfoFile, InputStream in,
+ boolean withCover) throws IOException {
MetaData meta = new MetaData();
meta.setTitle(getInfoTag(in, "TITLE"));
meta.setSubject(getInfoTag(in, "SUBJECT"));
meta.setType(getInfoTag(in, "TYPE"));
meta.setImageDocument(getInfoTagBoolean(in, "IMAGES_DOCUMENT", false));
- meta.setCover(BasicSupport.getImage(null, sourceInfoFile,
- getInfoTag(in, "COVER")));
+ if (withCover) {
+ meta.setCover(BasicSupport.getImage(null, sourceInfoFile,
+ getInfoTag(in, "COVER")));
+ }
try {
meta.setWords(Long.parseLong(getInfoTag(in, "WORDCOUNT")));
} catch (NumberFormatException e) {
meta.setCreationDate(getInfoTag(in, "CREATION_DATE"));
meta.setFakeCover(Boolean.parseBoolean(getInfoTag(in, "FAKE_COVER")));
- if (meta.getCover() == null) {
+ if (withCover && meta.getCover() == null) {
meta.setCover(BasicSupport.getDefaultCover(meta.getSubject()));
}
@Override
protected MetaData getMeta(URL source, InputStream in) throws IOException {
try {
- MetaData meta = InfoReader.readMeta(new File(new File(source
- .toURI()).getPath() + ".info"));
+ MetaData meta = InfoReader.readMeta(
+ new File(new File(source.toURI()).getPath() + ".info"),
+ true);
// Some old .info files don't have those now required fields...
String test = meta.getTitle() == null ? "" : meta.getTitle();