package com.gmail.dpierron.calibre.opds;
/**
* CatalogManager
* ~~~~~~~~~~~~~~
* Class to store context about the current Catalog that is being generated,
* and to provide methods for manipulating Catalog generic information.
*
* NOTE: As there should only ever be one instance of this class all global
* variables and methods are declared static
*/
import com.gmail.dpierron.calibre.cache.CachedFile;
import com.gmail.dpierron.calibre.cache.CachedFileManager;
import com.gmail.dpierron.calibre.configuration.ConfigurationHolder;
import com.gmail.dpierron.calibre.configuration.ConfigurationManager;
import com.gmail.dpierron.calibre.configuration.DeviceMode;
import com.gmail.dpierron.calibre.datamodel.*;
import com.gmail.dpierron.calibre.datamodel.filter.BookFilter;
import com.gmail.dpierron.calibre.gui.CatalogCallbackInterface;
import com.gmail.dpierron.tools.Helper;
import com.gmail.dpierron.tools.i18n.Localization;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
public class CatalogManager {
private final static org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(CatalogManager.class);
//-----------------------------------------
private static final boolean syncLog = true; // Set to true to get a log of the file copy process
//----------------------------------------- (If set false, code is optimised out by compiler)
public static BookFilter featuredBooksFilter;
// TODO Does not seem to be used any more - remove it?
// private static List<CachedFile> listOfFilesToCopy;
// The list of non-image files that need to be copied from the
// source library to the target library
private static List<String> listOfLibraryFilesToCopy;
// The list of image files that need to ce copied from the source library
private static Map<String, CachedFile> mapOfImagesToCopy;
// TODO: Itimpi: Does not seem to be needed any more?
// private static Map<String, Book> mapOfBookByPathToCopy;
private static Map<String, String> mapOfCatalogFolderNames;
// List of file in catalog that are unchanged
// TODO - Not yet used - intended to help with optimisation
private static List<CachedFile> listOfUnchangedCatalogFiles;
private static String securityCode;
private static String securityCodeAndSeparator;
private static String initialUrl;
private static CachedFile libraryFolder; // Folder holding the Calibre library
private static int libraryFolderPathLength;
private static File generateFolder; // Location where catalog is generated
private static int generateFolderPathLength;
private static File catalogFolder; // Location where catalog is to be placed
private static int catalogFolderPathLength;
private static File targetFolder; // Location where final catalog will be copied to (if required)
private static int targetFolderPathLength;
private static PrintWriter syncLogFile; // File to be used for the Sync log
public static HtmlManager htmlManager;
public static ThumbnailManager thumbnailManager;
public static ImageManager coverManager;
public static CatalogCallbackInterface callback;
public static SecurityManager securityManager;
public static ConfigurationHolder currentProfile;
// This is the date format used within the book details.
// At the moment it is either a full date or jsut the year
// If users ask for more flexibility the coniguration options can be re-visited.
public static DateFormat titleDateFormat;
// This is the date format that is to be used in the titles for the Recent Books sub-catalog section
// It is currently a hard-coded format. If there is user feedback suggestion that variations are
// desireable then it could be come a configurable option
public static DateFormat bookDateFormat;
// Tags that the user has specified should not be included
private static List<Tag> tagsToIgnore;
public static Map<String, BookFilter> customCatalogsFilters;
// We set this to false if the cover flow mode has changed (or is unknown)
public static Boolean coverFlowModeSame;
// Date previous catalog was generated (0 if not known) for use by optimizer
public static long generatedDate;
// Configuration used to generate a particular catalog.
// Can be used to help decide what optimisations are possible.
public static ConfigurationHolder generatedConfig;
// Some Stats to accumulate
// NOTE. We make them public to avoid needing getters
public static long statsXmlChanged; // Count of files where we realise during generation that XML unchanged
public static long statsXmlDiscarded; // count of XML files generated and then discarded as not needed for final catalog
public static long statsHtmlChanged; // Count of files where HTML not generated as XML unchanged.
public static long statsXmlUnchanged; // Count of files where we realise during generation that XML unchanged
public static long statsHtmlUnchanged; // Count of files where HTML not generated as XML unchanged.
public static long statsCopyExistHits; // Count of Files that are copied because target does not exist
public static long statsCopyLengthHits; // Count of files that are copied because lengths differ
public static long statsCopyDateMisses; // Count of files that are not copied because source older
public static long statsCopyCrcHits; // Count of files that are copied because CRC different
public static long statsCopyCrcMisses; // Count of files copied because CRC same
public static long statsCopyToSelf; // Count of cases where copy to self requested
public static long statsCopyUnchanged; // Count of cases where copy skipped because file was not even generated
public static long statsCopyDeleted; // Count of files/folders deleted during copy process
public static long statsBookUnchanged; // We detected that the book was unchanged since last run
public static long statsBookChanged; // We detected that the book was changed since last run
public static long statsCoverUnchanged; // We detected that the cover was unchanged since last run
public static long statsCoverChanged; // We detected that the cover was changed since last run
// public CatalogManager() {
public static void initialize() {
// super();
// Avoid superflous settings of static object!
securityCode = ConfigurationManager.getCurrentProfile().getSecurityCode();
if (Helper.isNullOrEmpty(securityCode)) {
Random generator = new Random(System.currentTimeMillis());
securityCode = Integer.toHexString(generator.nextInt());
ConfigurationManager.getCurrentProfile().setSecurityCode(securityCode);
}
if (! ConfigurationManager.getCurrentProfile().getCryptFilenames()) {
securityCode = "";
}
initialUrl = securityCode;
if (securityCode.length() != 0) initialUrl += Constants.SECURITY_SEPARATOR;
initialUrl += Constants.INITIAL_URL;
resetStats();
// TODO Decide if these should be conditional or just done every time!
if (htmlManager == null) htmlManager = new HtmlManager();
if (thumbnailManager == null) thumbnailManager = ImageManager.newThumbnailManager();
if (coverManager==null) coverManager = ImageManager.newCoverManager();
if (securityManager==null) securityManager = new SecurityManager();
if (currentProfile==null) currentProfile = ConfigurationManager.getCurrentProfile();
if (bookDateFormat==null) bookDateFormat = currentProfile.getPublishedDateAsYear() ? new SimpleDateFormat("yyyy") : SimpleDateFormat.getDateInstance(DateFormat.LONG,currentProfile.getLanguage());
if (titleDateFormat==null) titleDateFormat = SimpleDateFormat.getDateInstance(DateFormat.LONG, currentProfile.getLanguage());
if (customCatalogsFilters==null) customCatalogsFilters = new HashMap<String, BookFilter>();
}
public static void reset() {
libraryFolder = null;
generateFolder = null;
targetFolder = null;
catalogFolder = null;
syncLogFile = null;
featuredBooksFilter = null;
// listOfFilesToCopy = new LinkedList<CachedFile>();
listOfLibraryFilesToCopy = new LinkedList<String>();
// mapOfBookByPathToCopy = new HashMap<String, Book>();
mapOfCatalogFolderNames = new HashMap<String, String>();
// bookEntriesFiles = new LinkedList<File>();
bookDetailsCustomColumns = null;
listOfUnchangedCatalogFiles = new LinkedList<CachedFile>();
mapOfImagesToCopy = new HashMap<String, CachedFile>();
htmlManager = null;
thumbnailManager = null;
coverManager = null;
securityManager = null;
currentProfile = null;
titleDateFormat = null;
bookDateFormat = null;
tagsToIgnore = null;
customCatalogsFilters = null;
JDOMManager.reset();
securityCode = "";
securityCodeAndSeparator = null;
coverFlowModeSame = null;
generatedDate = 0;
generatedConfig = null;
resetStats();
}
private static void resetStats() {
statsXmlChanged
= statsXmlDiscarded
= statsHtmlChanged
= statsXmlUnchanged
= statsHtmlUnchanged
= statsCopyExistHits
= statsCopyLengthHits
= statsCopyCrcHits
= statsCopyCrcMisses
= statsCopyDateMisses
= statsCopyUnchanged
= statsBookUnchanged
= statsBookChanged
= statsCoverUnchanged
= statsCoverChanged = 0;
}
public static String getSecurityCode() {
if (securityCode == null) {
securityCode = CatalogManager.getSecurityCode();
}
return securityCode;
}
public static String getSecurityCodeAndSeparator() {
if (securityCodeAndSeparator == null) {
securityCodeAndSeparator = securityCode + (securityCode.length() == 0 ? "" : Constants.SECURITY_SEPARATOR);
}
return securityCodeAndSeparator;
}
public static String getInitialUr() {
return initialUrl;
}
/**
* Get the current catalog folder
* @return
*/
/**
* We always generate into a temporary folder
* We need to create one if we do not already have one setup
*
* @return
*/
public static File getGenerateFolder() {
if (generateFolder == null) try {
// Initialise area for generating the catalog files
File temp = File.createTempFile("calibre2opds", "");
String tempPath = temp.getAbsolutePath();
temp.delete(); // Remove file just created as we are going to create a folder there instead
// See if user has specified a specific location for the TEMP folder
String tempDirectory = System.getenv("CALIBRE2OPDS_TEMP");
if (Helper.isNotNullOrEmpty(tempDirectory)) {
tempPath = tempDirectory + Constants.FOLDER_SEPARATOR + temp.getName();
}
generateFolder = new File(tempPath);
if (logger.isTraceEnabled())
logger.trace("generateFolder set to " + generateFolder);
generateFolder.mkdir();
generateFolder.deleteOnExit();
generateFolderPathLength = getGenerateFolder().getAbsolutePath().length();
logger.info("Temporary Files folder: " + generateFolder.getAbsolutePath());
} catch (IOException e) {
// Do not believe this is possible
// If it happens we need to abort the run!
logger.error("Unable to create temp folder");
System.exit(-8);
}
return generateFolder;
}
public static int getGenerateFolderpathLength() {
assert generateFolderPathLength != 0;
return generateFolderPathLength;
}
/**
* Get the library folder associated with the current catalog generation
*
* @return
*/
public static CachedFile getLibraryFolder() {
if (libraryFolder == null) {
libraryFolder = CachedFileManager.addCachedFile(currentProfile.getDatabaseFolder());
libraryFolderPathLength = libraryFolder.getAbsolutePath().length();
}
return libraryFolder;
}
public static int getLibraryFolderPathLength() {
assert libraryFolderPathLength != 0;
return libraryFolderPathLength;
}
/**
* Get the target folder for the current catalog generation
*
* @return
*/
public static File getTargetFolder() {
if (targetFolder == null) {
targetFolder = currentProfile.getTargetFolder();
targetFolderPathLength = targetFolder == null ? 0 : targetFolder.getAbsolutePath().length();
}
return targetFolder;
}
public static int getTargetFolderPathLength() {
assert targetFolderPathLength != 0;
return targetFolderPathLength;
}
/**
* Only used in Nook mode!
*/
public static void setTargetFolder(File f) {
assert currentProfile.getDeviceMode().equals(DeviceMode.Nook);
targetFolder = f;
targetFolderPathLength = targetFolder.getAbsolutePath().length();
}
public static boolean getSyncLog() {
return syncLog;
}
private static PrintWriter getSyncLogFile() {
if (! syncLog) return null;
if (syncLogFile == null) try {
syncLogFile = new PrintWriter(ConfigurationManager.getConfigurationDirectory() + "/" + Constants.LOGFILE_FOLDER + "/" + Constants.SYNCFILE_NAME);
} catch (IOException e) {
// This should not happen
logger.error("Unable to create SyncLog File");
System.exit(-7);
}
return syncLogFile;
}
/**
* Print a line if sync loggining is active
* Accepts formatting parameters like a printf method
*
* @param s formatting string/fixed text
* @param args (optionla) arguments to use with formatting
*/
public static void syncLogPrintln(String s, Object ... args ) {
if (syncLog) {
getSyncLogFile().print(String.format(s,args));
getSyncLogFile().println();
}
return;
}
public static void syncLogClose() {
if (syncLogFile != null) {
syncLogFile.close();
}
}
/**
* Get the name of the catalog folder.
* It will take into account the current mode if relevant
* @return
*/
public static String getCatalogFolderName() {
if (ConfigurationManager.getCurrentProfile().getDeviceMode() == DeviceMode.Nook)
return Constants.NOOK_CATALOG_FOLDERNAME;
else
return ConfigurationManager.getCurrentProfile().getCatalogFolderName();
}
/**
* Get the location where the generated catalog must be copied to
*
* @return
*/
public static File getCatalogFolder() {
return catalogFolder;
}
/**
* Set the location where the generated catalog must be copied to
*
* @param folder
*/
public static void setCatalogFolder(File folder)
{
catalogFolder = folder;
catalogFolderPathLength = catalogFolder.getAbsolutePath().length();
}
public static int getCatalogFolderPathLength() {
assert catalogFolderPathLength != 0;
return catalogFolderPathLength;
}
/**
*
* @return
*/
public static List<String> getListOfFilesPathsToCopy() {
return listOfLibraryFilesToCopy;
}
/**
*
* @param pathToCopy
* @return
*/
/*
public static Book getBookByPathToCopy(String pathToCopy) {
return mapOfBookByPathToCopy.get(pathToCopy);
}
*/
/**
*
* @param file
*/
public static void addFileToTheMapOfFilesToCopy(CachedFile file) {
addFileToTheMapOfLibraryFilesToCopy(file, null);
}
/**
*
* @param file
* @param book
*/
public static void addFileToTheMapOfLibraryFilesToCopy(CachedFile file, Book book) {
final String databasePath = ConfigurationManager.getCurrentProfile().getDatabaseFolder().getAbsolutePath();
final int databasePathLength = databasePath.length() + 1;
if (file == null)
return;
String filePath = file.getAbsolutePath();
// Lets not copy files outside the database folder
if (!filePath.startsWith(databasePath) ) {
if (filePath.endsWith(Constants.DEFAULT_THUMBNAIL_FILENAME)
|| filePath.endsWith(Constants.CALIBRE_COVER_FILENAME)) {
// Expected to happen when cover image missing
return;
}
logger.warn("addFileToTheMapOfLibraryFilesToCopy: adding file not in library area! (" + filePath + ")");
return;
}
String relativePath = filePath.substring(databasePathLength);
if (! listOfLibraryFilesToCopy.contains(relativePath)) {
listOfLibraryFilesToCopy.add(relativePath);
}
// TODO Work out if this following line is ever needed
// TODO If not we can eliminate the version that passed in book as a parameter
// if (book != null) {
// assert ! mapOfBooksByPathToCopy.contains(relativePath);
// mapOfBookByPathToCopy.put(relativePath, book);
// }
// TODO It appear we no longer need the following list either.
// TODO Perhaps it is necessary to validate what is was intended for?
// if (listOfFilesToCopy.contains(file)) {
// logger.trace("addFileToTheMapOfLibraryFilesToCopy: listOfFilesToCopy alread contains file " + file);
// } else {
// listOfFilesToCopy.add(file);
// }
}
/**
* Add a file to the map of image files that are to be copied
* to the catalog (assuming this option is even set!)
*/
public static void addImageFileToTheMapOfCatalogImages(String key, CachedFile file) {
assert file != null : "Program Error: attempt to add 'null' file to image map";
assert (file.getName().equals("c2o_thumbnail.jpg")
|| file.getName().equals("c2o_resizedcover.jpg")
|| file.getName().equals(Constants.CALIBRE_COVER_FILENAME)
|| file.getName().equals(Constants.DEFAULT_IMAGE_FILENAME)):
"Program Error: Unexpected name '" + file.getName() + "' when trying to add image to map";
if (! mapOfImagesToCopy.containsKey(key)) {
mapOfImagesToCopy.put(key, file);
}
}
/**
*
* @return
*/
public static Map<String,CachedFile> getMapOfCatalogImages() {
return mapOfImagesToCopy;
}
/**
* Get the URL that is used to reference a particular file.
* If not alreaady present then added it to the map of files
* that are currently in the catalog.
*
* It will have the appropriate suffix added to ensure that it
* correctly references the current or parent folder.
*
* @param catalogFileName
* @return
*/
public static String getCatalogFileUrl(String catalogFileName, Boolean inSubDir) {
assert Helper.isNotNullOrEmpty(catalogFileName);
int pos = catalogFileName.indexOf(Constants.FOLDER_SEPARATOR);
String catalogFolderName = mapOfCatalogFolderNames.get(catalogFileName);
if (Helper.isNullOrEmpty(catalogFolderName)) {
storeCatalogFile(catalogFileName);
// catalogFolderName = mapOfCatalogFolderNames.get(catalogFileName);
catalogFolderName = pos == -1 ? "" : catalogFileName.substring(0,pos);
}
return (inSubDir ? Constants.PARENT_PATH_PREFIX : Constants.CURRENT_PATH_PREFIX)
+ FeedHelper.urlEncode(catalogFolderName)
+ (pos == - 1 ? "" : Constants.FOLDER_SEPARATOR)
+ FeedHelper.urlEncode(catalogFileName.substring(pos + 1));
}
/**
* Get the Folder that a particular catalog file belongs in
*
* @param pCatalogFileName
* @return
*/
public static String getFolderName(String pCatalogFileName) {
if (Helper.isNullOrEmpty(pCatalogFileName))
return "";
int pos = pCatalogFileName.indexOf(Constants.FOLDER_SEPARATOR);
return (pos == -1) ? pCatalogFileName : pCatalogFileName.substring(0,pos);
}
/**
* Set up an entry for the given file in the catalog.
* Checks to see if the file is already present and if not adds it
*
* @param catalogFileName The name of the file to be stored. Includes folder if relevant
* @return File object corresponding to the given path
*/
public static File storeCatalogFile(String catalogFileName) {
File folder = null;
String folderName;
int pos = catalogFileName.indexOf(Constants.FOLDER_SEPARATOR); // Look for catalog name terminator being present
if (pos != -1 ) {
// truncate name supplied to use to only be folder part
folderName = catalogFileName.substring(0, pos);
folder = new File(getGenerateFolder(), folderName);
} else {
folderName = "";
folder = new File(getGenerateFolder(), folderName);
}
if (!folder.exists()) {
folder.mkdirs();
}
mapOfCatalogFolderNames.put(catalogFileName, folderName);
File result = new File(getGenerateFolder(), catalogFileName);
return result;
}
private static List<CustomColumnType> bookDetailsCustomColumns = null;
/**
* Get the list of curom columns that are to be included in Book Details.
* If we do not recognize any of them they are ignored as an earlier
* validation tst will have checked this with the user.
*
* @return
*/
public static List<CustomColumnType> getBookDetailsCustomColumns() {
if (bookDetailsCustomColumns == null) {
List<CustomColumnType> types = DataModel.getListOfCustomColumnTypes();
if (types == null) {
logger.warn("getBookDetailsCustomColumns: No custom columns read from database.");
return null;
}
bookDetailsCustomColumns = new LinkedList<CustomColumnType>();
for (String customColumnLabel : ConfigurationManager.getCurrentProfile().getTokenizedBookDetailsCustomColumns()) {
if (customColumnLabel.startsWith("#")) {
customColumnLabel = customColumnLabel.substring(1);
}
for (CustomColumnType type : types) {
if (type.getLabel().toUpperCase().equals(customColumnLabel.toUpperCase())) {
bookDetailsCustomColumns.add(type);
}
}
}
}
return bookDetailsCustomColumns;
}
/**
* TODO Not yet used - planned for optimisation
* Track the list of files that are part of the catalog,
* but are are unchanged since the last run
* @param f
*/
public static void addUnchangedFileToList (CachedFile f) {
if (! listOfUnchangedCatalogFiles.contains(f)) {
listOfUnchangedCatalogFiles.add(f);
}
}
/*
Make these properties public to avoid the need for simpe get/set routines that do nothing else!
public static BookFilter getFeaturedBooksFilter() {
return featuredBooksFilter;
}
public static void setFeaturedBooksFilter(BookFilter featuredBooksFilter) {
this.featuredBooksFilter = featuredBooksFilter;
}
public static List<Composite<String, String>> getCustomCatalogs() {
return customCatalogs;
}
public static void setCustomCatalogs (List<Composite<String, String>> pcustomCatalogs) {
customCatalogs = pcustomCatalogs;
}
public static Map<String, BookFilter> getCustomCatalogFilters () {
return customCatalogsFilters;
}
public static void setCustomCatalogsFilter (Map<String, BookFilter> pcustomCatalogsFilters) {
customCatalogsFilters = pcustomCatalogsFilters;
}
*/
/**
* Get the list of tags to ignore. If it has not been done,
* convert the list of tags to ignore from the string
* representation to the appropriate object representation
* as this is more effecient in later processing.
*
* @return
*/
public static List<Tag> getTagsToIgnore () {
if (tagsToIgnore == null) {
tagsToIgnore = new LinkedList<Tag>();
for (Tag tag : DataModel.getListOfTags()) {
List<String> regextagsToIgnore = currentProfile.getRegExTagsToIgnore();
for (String regexTag : regextagsToIgnore) {
if (tag.getName().toUpperCase().matches("^" + regexTag)) {
if (! tagsToIgnore.contains(tag)) {
tagsToIgnore.add(tag);
}
}
}
}
}
return tagsToIgnore;
}
/**
* Determine if images need to be resized
*
* @param filename
* @param TargetSize
* @return
*/
private static boolean isImagesResized(String filename, int TargetSize) {
File sizeFile = new File(filename);
return false;
}
/**
* Check if specified file different in generate and target folders.
*
* Ensures that there are entries in the cache for these files.
*
* The following checks are made as part of determining if the
* files are iendtical:
* - The file must exist in both locations
* = The sizes must be the same
* - The CRC's must match
*
* If these criteria are saitisfied then the 'Changed' attribute is
* cleared on the cache entry for both sourcc and target.
*
* @param sourcefile
* @return
*/
public static boolean isSourceFileSameAsTargetFile(CachedFile sourcefile, CachedFile targetfile) {
// Assumptions we should be able to make
assert sourcefile != null;
assert sourcefile.exists() == true;
assert targetfile != null;
// If target does not exist then they cannot be the same
// (even if the generate file appears unchanged
if (! targetfile.exists()) {
assert targetfile.isChanged() == true;
return false;
}
// See if we already know they match
if (! sourcefile.isChanged()) {
targetfile.setChanged(false);
}
if (! targetfile.isChanged()) {
return true;
}
// Must be different if length changed
if (sourcefile.length() != targetfile.length()) {
assert targetfile.isChanged() == true;
return false;
}
// If length appears identical need to check CRC
targetfile.setChanged(sourcefile.getCrc() != targetfile.getCrc());
return (targetfile.isChanged() == false);
}
/**
*
* Check if specified file different in generate and catalog folders.
* The filename rovided should be the path relative to the folders
* in question and not include the path itself.
*
* Uses isGenerateFileSameAsTargetFile() supplying catalog folder as target.
*
* @param filename
* @return
*/
public static boolean isGenerateFileSameAsCatalogFile(String filename) {
// Create absolute path entries and ensure theya re in the cache
CachedFile generateFile = CachedFileManager.addCachedFile(CatalogManager.getGenerateFolder().getAbsolutePath() + File.separator + filename);
CachedFile catalogFile = CachedFileManager.addCachedFile(CatalogManager.getCatalogFolder().getAbsolutePath() + File.separator + filename);
assert generateFile != null;
assert catalogFile != null;
return isSourceFileSameAsTargetFile(generateFile, catalogFile);
}
// Collect some information about the RAM usage while running
private static String ramPoolMeasurement[] = new String[ManagementFactory.getMemoryPoolMXBeans().size()];
private static String ramPoolName[] = new String[ManagementFactory.getMemoryPoolMXBeans().size()];
private static String ramPoolType[] = new String[ManagementFactory.getMemoryPoolMXBeans().size()];
private static long ramPoolCommitted[] = new long[ManagementFactory.getMemoryPoolMXBeans().size()];
private static long ramPoolInit[] = new long[ManagementFactory.getMemoryPoolMXBeans().size()];
private static long ramPoolMax[] = new long[ManagementFactory.getMemoryPoolMXBeans().size()];
private static long ramPoolUsed[] = new long[ManagementFactory.getMemoryPoolMXBeans().size()];
/**
* Provide an end-of-run summary of RAM usage
*/
public static void reportRamUsage(String measurementPoint) {
logger.info("");
logger.info("Java VM RAM Usage " + measurementPoint);
logger.info(String.format(" %-20s %-15s%10s%10s%10s%10s",
"NAME",
"TYPE",
"COMMITTED",
"INIT",
"MAX",
"USED"));
for (int i = 0 ; i < ramPoolType.length; i++) {
logger.info(String.format(" %-20s %-15s%10d MB%7d MB%7d MB%7d MB",
ramPoolName[i],
ramPoolType[i],
ramPoolCommitted[i] / (2<<20),
ramPoolInit[i] / (2<<20),
ramPoolMax[i] / (2<<20),
ramPoolUsed[i] / (2<<20)));
}
logger.info("");
}
/**
* Record the RAM usage at the specified measuring point.
*
* If in a debug mode we also output to log file, otherwise
* just accumlate results.
*/
public static void recordRamUsage(String measurementPoint) {
List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
if (ramPoolName.length != pools.size()) {
// This is safety check - not sure it can really happen
logger.error("Unexpected change in number of RAM areas (was " + ramPoolName.length + ", now " + pools.size());
} else {
for (int i = 0; i < pools.size() ; i++) {
MemoryPoolMXBean pool = pools.get(i);
if (ramPoolName[i] == null)
ramPoolName[i] = pool.getName();
if (ramPoolType[i] == null)
ramPoolType[i] = pool.getType().toString();
if (! ramPoolName[i].equals(pool.getName())) {
logger.error("Mismatch on RAM Pool " + i + " name (expected " + ramPoolName[i] + ", got " + pool.getName() + "0" );
} else {
MemoryUsage usage = pool.getUsage();
if (usage.getCommitted() > ramPoolCommitted[i])
ramPoolCommitted[i] = usage.getCommitted();
if (usage.getInit() > ramPoolInit[i])
ramPoolInit[i] = usage.getInit();
if (usage.getMax() > ramPoolMax[i])
ramPoolMax[i] = usage.getMax();
if (usage.getUsed() > ramPoolUsed[i])
ramPoolUsed[i] = usage.getUsed();
if (logger.isDebugEnabled()) {
if (i == 0) {
logger.info("");
logger.info("Java VM RAM Usage " + measurementPoint);
logger.info(String.format(" %-20s %-15s%10s%10s%10s%10s", "NAME", "TYPE", "COMMITTED", "INIT", "MAX", "USED"));
}
logger.info(String.format(" %-20s %-15s%10d MB%7d MB%7d MB%7d MB",
pool.getName(),
pool.getType().toString(),
usage.getCommitted() / (2 << 20),
usage.getInit() / (2 << 20),
usage.getMax() / (2 << 20),
usage.getUsed() / (2 << 20)));
logger.info("");
}
}
}
}
}
private static File getOptimizeFile() {
return new File(getCatalogFolder(), "c2o_optimizer.dat");
}
/**
* Save information that can be used by the optiizer on
* the next run to see where we can avoid generating new
* catalog files unnecesarily. The information we
* currently save is:
* - CoverFlowMode used (it changed then all relevant HTML files need regenerating)
* - generation date (used to see if book record changed since last generate)
* - max page size
* - max split levels
* - entries before split
* - summary max length in lists
* - summary max length in book details
*/
public static void saveOptimizerData() {
File f = getOptimizeFile();
if (! f.getParentFile().exists()) {
f.getParentFile().mkdirs(); // create the path if it does not already exist
logger.info(Localization.Main.getText("optimizer.createPath", f.getParentFile()));
}
// Perhaps this should always exist - but lets play safe!
if (generatedConfig == null) {
generatedConfig = new ConfigurationHolder(f);
logger.info(Localization.Main.getText("optimizer.createDefaults"));
}
// Now set any settings of use to optimizer from current profile
generatedConfig.setMaxBeforePaginate(currentProfile.getMaxBeforePaginate());
generatedConfig.setMaxSplitLevels(currentProfile.getMaxSplitLevels());
generatedConfig.setMaxBeforeSplit(currentProfile.getMaxBeforeSplit());
generatedConfig.setMaxSummaryLength(currentProfile.getMaxSummaryLength());
generatedConfig.setMaxBookSummaryLength(currentProfile.getMaxBookSummaryLength());
generatedConfig.setSecurityCode(Long.toString(System.currentTimeMillis())); // Reuse security code as time
generatedConfig.setBrowseByCover(currentProfile.getBrowseByCover());
generatedConfig.save();
logger.info(Localization.Main.getText("optimizer.savedFile", f));
}
/**
* Once the genration phase has completed and file sync is started then the
* state of the library is indeterminate from an optimizer perspective.
* Removing the optimizer file during this stage is therefore needed.
*/
public static void deleteoptimizerFile() {
try {
getOptimizeFile().delete();
} catch (Exception e) {
// Ignore errors on delete
}
}
/**
* Load information from previous generate that is relevant to this run to work
* out what optimisations are valid for this run.
* - If cover flow mode is unchanged then we can avoid trying to generate the
* corresponding HTML file if the CML file is known to be unchanged.
*/
public static void loadOptimizerDetail() {
// Set some default values for optimisations in case we cannot load better ones
File f = getOptimizeFile();
coverFlowModeSame = false;
generatedDate = 0;
if (currentProfile.getDisableOptimizer()) {
logger.info(Localization.Main.getText("optimizer.disabled"));
} else {
if (!f.getParentFile().exists()) {
logger.info(Localization.Main.getText("optimizer.catalogNotFound", f.getParentFile()));
} else {
if (!f.exists()) {
logger.info(Localization.Main.getText("optimizer.fileNotFound", f));
} else {
try {
generatedConfig = new ConfigurationHolder(getOptimizeFile());
generatedConfig.load();
logger.info(Localization.Main.getText("optimizer.loadedFile", f));
generatedDate = Long.parseLong(generatedConfig.getSecurityCode());
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy-HH:mm");
logger.info(Localization.Main.getText("optimizer.previousCatalogOn", sdf.format(generatedDate)));
boolean oldCoverFlowMode = generatedConfig.getBrowseByCover();
boolean newCoverFlowMode = currentProfile.getBrowseByCover();
coverFlowModeSame = (oldCoverFlowMode == newCoverFlowMode);
if (!coverFlowModeSame) {
logger.info(Localization.Main.getText("optimizer.coverFlowChanged"));
}
deleteoptimizerFile(); // Remove in case run fails when state will be unknown
} catch (Exception e) {
// If we failed in any waythen defauts set earlier are ued
logger.info(Localization.Main.getText("optimizer.fileNotLoaded"), f);
}
}
}
}
deleteoptimizerFile(); // Remove in case run fails when state will be unknown
}
/**
* Work out if the cover mode (if any ) stroed in the catatlog
* is the same as the one we want for this run.
* @return
*/
public static Boolean IsCoverFlowModeSame() {
if (coverFlowModeSame == null) {
loadOptimizerDetail();
}
return coverFlowModeSame;
}
/**
* Get the generated ate (if any) for the provious version of the catalog
* This will be used to try and optimize the generation process as we can
* use the modifed date of books to see if they are unchanged since then
*
* @return
*/
public static long getLastGenerated() {
return generatedDate;
}
}