/*
* (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*
*/
package org.nuxeo.ecm.platform.pictures.tiles.service;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.Path;
import org.nuxeo.ecm.platform.commandline.executor.api.CommandException;
import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
import org.nuxeo.ecm.platform.picture.api.ImageInfo;
import org.nuxeo.ecm.platform.picture.magick.utils.ImageIdentifier;
import org.nuxeo.ecm.platform.picture.magick.utils.ImageResizer;
import org.nuxeo.ecm.platform.pictures.tiles.api.PictureTiles;
import org.nuxeo.ecm.platform.pictures.tiles.helpers.StringMaker;
import org.nuxeo.runtime.api.Framework;
/**
* Wraps the needed information about tiling a picture in order to manage cache. This includes:
* <ul>
* <li>original image stored on file system</li>
* <li>reduced images if any</li>
* <li>tiles already generated</li>
* </ul>
*
* @author tiry
*/
public class PictureTilingCacheInfo {
public static int SHRINK_DOWN_LIMIT_PX = 2000;
private static final Log log = LogFactory.getLog(PictureTilingCacheInfo.class);
protected String cacheKey;
protected String workingDir;
protected ImageInfo originalPictureInfos;
protected Map<Integer, ImageInfo> shrinkedImages;
protected List<Integer> shrinkedImagesWidths;
protected Map<String, PictureTiles> tilesSet;
protected String syncShrink = "oneOncePerInstance";
protected Date lastAccessTime;
protected void updateAccessTime() {
lastAccessTime = new Date();
}
public Date getLastAccessedTime() {
return lastAccessTime;
}
protected long getFileSize(String path) {
if (path == null)
return 0;
File file = new File(path);
if (file.exists()) {
return file.length();
} else
return 0;
}
public long getDiskSpaceUsageInBytes() {
long diskSpaceUsage = 0;
// original picture
diskSpaceUsage += getFileSize(originalPictureInfos.getFilePath());
// shrinked ones
for (Integer s : shrinkedImages.keySet()) {
diskSpaceUsage += getFileSize(shrinkedImages.get(s).getFilePath());
}
// tiles
for (String tileDef : tilesSet.keySet()) {
PictureTiles tiles = tilesSet.get(tileDef);
File tileDir = new File(tiles.getTilesPath());
if (tileDir.exists()) {
for (File tileFile : tileDir.listFiles()) {
diskSpaceUsage += tileFile.length();
}
}
}
return diskSpaceUsage;
}
public PictureTilingCacheInfo(String cacheKey, String workingDir, String filePath) throws CommandNotAvailable,
CommandException {
this.cacheKey = cacheKey;
this.workingDir = workingDir;
originalPictureInfos = ImageIdentifier.getInfo(filePath);
shrinkedImages = new HashMap<>();
shrinkedImagesWidths = new ArrayList<>();
tilesSet = new HashMap<>();
updateAccessTime();
}
public void addPictureTilesToCache(PictureTiles tiles) {
tilesSet.put(tiles.getTileFormatCacheKey(), tiles);
updateAccessTime();
}
public PictureTiles getCachedPictureTiles(int tileWidth, int tileHeight, int maxTiles) {
String ptKey = StringMaker.getTileFormatString(tileWidth, tileHeight, maxTiles);
updateAccessTime();
return tilesSet.get(ptKey);
}
public String getWorkingDir() {
return workingDir;
}
public String getOriginalPicturePath() {
return originalPictureInfos.getFilePath();
}
public String getTilingDir(int tileWidth, int tileHeight, int maxTiles) {
String dirPath = "tiles-" + tileWidth + "-" + tileHeight + "-" + maxTiles;
dirPath = new Path(workingDir).append(dirPath).toString();
log.debug("Target tiling dir=" + dirPath);
File dir = new File(dirPath);
if (!dir.exists()) {
dir.mkdir();
Framework.trackFile(dir, this);
}
return dirPath;
}
public ImageInfo getBestSourceImage(int tileWidth, int tileHeight, int maxTiles) {
updateAccessTime();
if ("JPEG".equals(originalPictureInfos.getFormat())) {
// since JPEG supports it we may strip it down
if ((originalPictureInfos.getHeight() > SHRINK_DOWN_LIMIT_PX)
|| (originalPictureInfos.getWidth() > SHRINK_DOWN_LIMIT_PX)) {
int neededWidth = tileWidth * maxTiles;
int neededHeight = tileHeight * maxTiles;
int shrinkedWidth = 0;
// JPG simplification work with 2 factor
if ((neededHeight > (originalPictureInfos.getHeight() / 2))
|| (neededWidth > (originalPictureInfos.getWidth() / 2))) {
return originalPictureInfos;
}
// avoid multiple shrink processing of the same image
synchronized (syncShrink) {
for (Integer swidth : shrinkedImagesWidths) {
if (swidth >= neededWidth)
shrinkedWidth = swidth;
else
break;
}
if (shrinkedWidth > 0) {
return shrinkedImages.get(new Integer(shrinkedWidth));
} else {
String shrinkedImagePath = new Path(workingDir).append(
"reduced-" + neededWidth + "x" + neededHeight + ".jpg").toString();
try {
ImageInfo shrinked = ImageResizer.resize(originalPictureInfos.getFilePath(),
shrinkedImagePath, neededWidth, neededHeight, -1);
shrinkedImagesWidths.add(new Integer(shrinked.getWidth()));
Collections.sort(shrinkedImagesWidths);
Collections.reverse(shrinkedImagesWidths);
shrinkedImages.put(new Integer(shrinked.getWidth()), shrinked);
return shrinked;
} catch (CommandNotAvailable | CommandException e) {
return originalPictureInfos;
}
}
}
} else
return originalPictureInfos;
} else
return originalPictureInfos;
}
public ImageInfo getOriginalPictureInfos() {
updateAccessTime();
return originalPictureInfos;
}
public void cleanUp() {
// original picture
File orgFile = new File(originalPictureInfos.getFilePath());
if (orgFile.exists())
orgFile.delete();
// shrinked ones
for (Integer s : shrinkedImages.keySet()) {
File skFile = new File(shrinkedImages.get(s).getFilePath());
if (skFile.exists())
skFile.delete();
}
// tiles
for (String tileDef : tilesSet.keySet()) {
PictureTiles tiles = tilesSet.get(tileDef);
File tileDir = new File(tiles.getTilesPath());
if (tileDir.exists()) {
for (File tileFile : tileDir.listFiles()) {
tileFile.delete();
}
}
}
}
public void partialCleanUp(long targetDeltaInKB) {
long deletedKB = 0;
// tiles
for (String tileDef : tilesSet.keySet()) {
PictureTiles tiles = tilesSet.get(tileDef);
File tileDir = new File(tiles.getTilesPath());
if (tileDir.exists()) {
for (File tileFile : tileDir.listFiles()) {
deletedKB += tileFile.length() / 1000;
tileFile.delete();
if (deletedKB > targetDeltaInKB)
return;
}
}
}
// shrinked ones
for (Integer s : shrinkedImages.keySet()) {
File skFile = new File(shrinkedImages.get(s).getFilePath());
if (skFile.exists()) {
deletedKB += skFile.length() / 1000;
skFile.delete();
if (deletedKB > targetDeltaInKB)
return;
}
}
// original picture
File orgFile = new File(originalPictureInfos.getFilePath());
if (orgFile.exists())
orgFile.delete();
}
}