package com.gmail.dpierron.calibre.opds;
/**
*
*/
import com.gmail.dpierron.calibre.cache.CachedFile;
import com.gmail.dpierron.calibre.cache.CachedFileManager;
import com.gmail.dpierron.calibre.configuration.ConfigurationManager;
import com.gmail.dpierron.calibre.datamodel.Book;
import com.gmail.dpierron.tools.i18n.Localization;
import com.gmail.dpierron.tools.Helper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
public abstract class ImageManager {
private final static Logger logger = LogManager.getLogger(ImageManager.class);
// private Map<File, File> generatedImages;
private int imageHeight = 1;
private boolean imageSizeChanged; // Set true to force regeneration of images
private long timeInImages;
private static int countOfImagesGenerated;
abstract String getResizedFilename();
abstract String getResizedFilenameOld(Book book);
abstract String getDefaultResizedFilename();
abstract String getImageHeightDat();
// CONSTRUCTOR
ImageManager(int imageHeight) {
reset();
this.imageHeight = imageHeight;
CachedFile imageSizeFile = CachedFileManager.addCachedFile(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), getImageHeightDat());
FeedHelper.checkFileNameIsNewStandard(imageSizeFile, CachedFileManager.addCachedFile(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), imageSizeFile.getName().substring(4)));
imageSizeChanged = true; // Assume true if sizefile does not exist
if (imageSizeFile.exists()) {
try {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(imageSizeFile));
String line = in.readLine(); // Skip the comment line
line = in.readLine();
int oldSize = Integer.valueOf(line);
imageSizeChanged = oldSize != imageHeight;
} finally {
if (in != null) {
in.close();
}
}
} catch (Exception e) {
// we don't care about errors, let's just say size has changed
if (logger.isDebugEnabled())
logger.debug("Failed to read size from file " + imageSizeFile);
imageSizeChanged = true;
} finally {
// #c2o-238
// If the image size has changed delete the height file in case generate goes wrong!
// It will be recreated if we successfully complete the run. We do not need to delete
// it if the image size is unchanged as in this case exiting image files are correct size.
if (imageSizeChanged) {
imageSizeFile.delete();
CachedFileManager.removeCachedFile(imageSizeFile);
}
}
}
}
// METHODS and PROPERTIES
public void reset () {
// generatedImages = new HashMap<File, File>();
countOfImagesGenerated = 0;
timeInImages = 0;
}
public final static ThumbnailManager newThumbnailManager() {
return new ThumbnailManager(ConfigurationManager.getCurrentProfile().getThumbnailHeight());
}
public final static ImageManager newCoverManager() {
return new CoverManager(ConfigurationManager.getCurrentProfile().getCoverHeight());
}
/*
public void setImageToGenerate(CachedFile resizedCoverFile, CachedFile coverFile) {
if (!generatedImages.containsKey(resizedCoverFile)) {
generateImage(coverFile,resizedCoverFile);
generatedImages.put(resizedCoverFile, coverFile);
}
}
*/
boolean hasImageSizeChanged() {
return imageSizeChanged;
}
/**
* Get the URI for a cover image
* (on the assumption it is in the library)
*
* @param book
* @return
*/
String getImageUri(Book book) {
String uriBase = ConfigurationManager.getCurrentProfile().getUrlBooks();
if (Helper.isNullOrEmpty(uriBase)) {
uriBase = Constants.LIBRARY_PATH_PREFIX;
}
return uriBase + FeedHelper.urlEncode(book.getPath() + "/" + getResizedFilename(), true);
}
/**
* Save the height of the images that we havfe generated.
*
* For efficency reasons, this method is expected to be called once
* after a particular catalog generation run has completed
*/
public void writeImageHeightFile() {
File imageSizeFile = new File(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), getImageHeightDat());
try {
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter(imageSizeFile));
out.println("## Automatically generated config file. DO NOT EDIT!");
out.println(Integer.toString(imageHeight));
} finally {
if (out != null)
out.close();
}
} catch (IOException e) {
// we don't care if the image height file cannot be written, image will be recomputed and that's all
}
CachedFileManager.addCachedFile(imageSizeFile).clearCachedInformation();
}
/**
* Get the contents of the specified file as base64 string
* so that we can embed the image in the xml/html file
*
* @return
*/
public String getFileToBase64Uri (File f) {
final String base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/";
int length = (int)f.length();
int paddingCount = (3 - (length % 3)) % 3;
byte[] data = new byte[length + 2];
// Preset potential padding bytes to null
for (int i = 0 ; i < 2; i++) {
data[length+(i)] = 0;
}
try {
DataInputStream in = new DataInputStream(new FileInputStream(f));
in.readFully(data,0,length);
in.close();
} catch (EOFException eof) {
// Ignore this error as we got all the data!
// However this is still unexpected so lets log it when tracing
if (logger.isTraceEnabled()) logger.trace("Unexpected EOF reading " + f);
} catch (IOException e) {
// Errors are unexpected - set dummy URI if that is the case
logger.warn("Unable to embed cover file " + f.getAbsolutePath() + "(IO Exception " + e.getMessage() + ")");
return Constants.PARENT_PATH_PREFIX + Constants.DEFAULT_IMAGE_FILENAME;
}
// process 3 bytes at a time, churning out 4 output bytes
StringBuffer encoded = new StringBuffer("");
for (int i = 0; i < length; i += 3) {
int j = ((data[i] & 0xff) << 16) +
((data[i + 1] & 0xff) << 8) +
(data[i + 2] & 0xff);
encoded = encoded.append("" + base64code.charAt((j >> 18) & 0x3f) +
"" + base64code.charAt((j >> 12) & 0x3f) +
"" + base64code.charAt((j >> 6) & 0x3f) +
"" + base64code.charAt(j & 0x3f));
}
data = null; // <ay not be necessary - but lets help garbage collector
// Add final padding characters
for (int i = paddingCount ; i > 0 ; i--) {
encoded.setCharAt(encoded.length() - i, '=');
}
return "data:image/"
// take image type from file extenson
+ f.getName().substring(f.getName().lastIndexOf('.')+1) + ";base64,"
+ encoded.toString();
}
/**
* * generate a single image file
*
* @param imageFile File for written fir output image
* @param coverFile File to be read for input image
*/
public void generateImage(CachedFile imageFile, CachedFile coverFile) {
assert imageFile != null ;
assert coverFile != null && coverFile.exists();
logger.debug("generateImage: " + imageFile.getAbsolutePath());
long now = System.currentTimeMillis();
try {
ImageFile ct = new ImageFile(coverFile.getAbsolutePath());
// If the image failed to load - no point in trying to generate it!
if (! ct.isImageLoaded()) return;
ct.getImage(imageHeight, ImageFile.VERTICAL);
ct.saveImage(imageFile, ImageFile.IMAGE_JPEG);
// bug #732821 Ensure file added to those cached for copying
CachedFile cf = CachedFileManager.addCachedFile(imageFile);
if (logger.isTraceEnabled())
logger.trace("generateImages: added new thumbnail file " + imageFile.getAbsolutePath() + " to list of files to copy");
countOfImagesGenerated++; // Update count of files processed
} catch (Exception e) {
CatalogManager.callback.errorOccured(Localization.Main.getText("error.generatingImage", imageFile.getAbsolutePath()), e);
if (logger.isTraceEnabled()) logger.trace(e);
} catch (Throwable t) {
logger.warn("Unexpected error trying to generate image " + coverFile.getAbsolutePath() + "\n" + t );
}
timeInImages += (System.currentTimeMillis() - now);
imageFile.clearCachedInformation();
}
public long getTimeInImages() {
return timeInImages;
}
public int getCountOfImagesGenerated() {
return countOfImagesGenerated;
}
}