001/* 002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 */ 018package org.nuxeo.ecm.platform.pictures.tiles.service; 019 020import java.io.File; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.common.utils.Path; 031import org.nuxeo.ecm.platform.commandline.executor.api.CommandException; 032import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable; 033import org.nuxeo.ecm.platform.picture.api.ImageInfo; 034import org.nuxeo.ecm.platform.picture.magick.utils.ImageIdentifier; 035import org.nuxeo.ecm.platform.picture.magick.utils.ImageResizer; 036import org.nuxeo.ecm.platform.pictures.tiles.api.PictureTiles; 037import org.nuxeo.ecm.platform.pictures.tiles.helpers.StringMaker; 038import org.nuxeo.runtime.api.Framework; 039 040/** 041 * Wraps the needed information about tiling a picture in order to manage cache. This includes: 042 * <ul> 043 * <li>original image stored on file system</li> 044 * <li>reduced images if any</li> 045 * <li>tiles already generated</li> 046 * </ul> 047 * 048 * @author tiry 049 */ 050public class PictureTilingCacheInfo { 051 052 public static int SHRINK_DOWN_LIMIT_PX = 2000; 053 054 private static final Log log = LogFactory.getLog(PictureTilingCacheInfo.class); 055 056 protected String cacheKey; 057 058 protected String workingDir; 059 060 protected ImageInfo originalPictureInfos; 061 062 protected Map<Integer, ImageInfo> shrinkedImages; 063 064 protected List<Integer> shrinkedImagesWidths; 065 066 protected Map<String, PictureTiles> tilesSet; 067 068 protected String syncShrink = "oneOncePerInstance"; 069 070 protected Date lastAccessTime; 071 072 protected void updateAccessTime() { 073 lastAccessTime = new Date(); 074 } 075 076 public Date getLastAccessedTime() { 077 return lastAccessTime; 078 } 079 080 protected long getFileSize(String path) { 081 if (path == null) 082 return 0; 083 File file = new File(path); 084 if (file.exists()) { 085 return file.length(); 086 } else 087 return 0; 088 } 089 090 public long getDiskSpaceUsageInBytes() { 091 long diskSpaceUsage = 0; 092 093 // original picture 094 diskSpaceUsage += getFileSize(originalPictureInfos.getFilePath()); 095 096 // shrinked ones 097 for (Integer s : shrinkedImages.keySet()) { 098 diskSpaceUsage += getFileSize(shrinkedImages.get(s).getFilePath()); 099 } 100 101 // tiles 102 for (String tileDef : tilesSet.keySet()) { 103 PictureTiles tiles = tilesSet.get(tileDef); 104 File tileDir = new File(tiles.getTilesPath()); 105 if (tileDir.exists()) { 106 for (File tileFile : tileDir.listFiles()) { 107 diskSpaceUsage += tileFile.length(); 108 } 109 } 110 } 111 112 return diskSpaceUsage; 113 } 114 115 public PictureTilingCacheInfo(String cacheKey, String workingDir, String filePath) throws CommandNotAvailable, 116 CommandException { 117 this.cacheKey = cacheKey; 118 this.workingDir = workingDir; 119 originalPictureInfos = ImageIdentifier.getInfo(filePath); 120 shrinkedImages = new HashMap<>(); 121 shrinkedImagesWidths = new ArrayList<>(); 122 tilesSet = new HashMap<>(); 123 updateAccessTime(); 124 } 125 126 public void addPictureTilesToCache(PictureTiles tiles) { 127 tilesSet.put(tiles.getTileFormatCacheKey(), tiles); 128 updateAccessTime(); 129 } 130 131 public PictureTiles getCachedPictureTiles(int tileWidth, int tileHeight, int maxTiles) { 132 String ptKey = StringMaker.getTileFormatString(tileWidth, tileHeight, maxTiles); 133 updateAccessTime(); 134 return tilesSet.get(ptKey); 135 } 136 137 public String getWorkingDir() { 138 return workingDir; 139 } 140 141 public String getOriginalPicturePath() { 142 return originalPictureInfos.getFilePath(); 143 } 144 145 public String getTilingDir(int tileWidth, int tileHeight, int maxTiles) { 146 String dirPath = "tiles-" + tileWidth + "-" + tileHeight + "-" + maxTiles; 147 dirPath = new Path(workingDir).append(dirPath).toString(); 148 149 log.debug("Target tiling dir=" + dirPath); 150 File dir = new File(dirPath); 151 152 if (!dir.exists()) { 153 dir.mkdir(); 154 Framework.trackFile(dir, this); 155 } 156 return dirPath; 157 } 158 159 public ImageInfo getBestSourceImage(int tileWidth, int tileHeight, int maxTiles) { 160 updateAccessTime(); 161 if ("JPEG".equals(originalPictureInfos.getFormat())) { 162 // since JPEG supports it we may strip it down 163 164 if ((originalPictureInfos.getHeight() > SHRINK_DOWN_LIMIT_PX) 165 || (originalPictureInfos.getWidth() > SHRINK_DOWN_LIMIT_PX)) { 166 int neededWidth = tileWidth * maxTiles; 167 int neededHeight = tileHeight * maxTiles; 168 int shrinkedWidth = 0; 169 170 // JPG simplification work with 2 factor 171 if ((neededHeight > (originalPictureInfos.getHeight() / 2)) 172 || (neededWidth > (originalPictureInfos.getWidth() / 2))) { 173 return originalPictureInfos; 174 } 175 176 // avoid multiple shrink processing of the same image 177 synchronized (syncShrink) { 178 for (Integer swidth : shrinkedImagesWidths) { 179 if (swidth >= neededWidth) 180 shrinkedWidth = swidth; 181 else 182 break; 183 } 184 185 if (shrinkedWidth > 0) { 186 return shrinkedImages.get(new Integer(shrinkedWidth)); 187 } else { 188 String shrinkedImagePath = new Path(workingDir).append( 189 "reduced-" + neededWidth + "x" + neededHeight + ".jpg").toString(); 190 try { 191 ImageInfo shrinked = ImageResizer.resize(originalPictureInfos.getFilePath(), 192 shrinkedImagePath, neededWidth, neededHeight, -1); 193 194 shrinkedImagesWidths.add(new Integer(shrinked.getWidth())); 195 Collections.sort(shrinkedImagesWidths); 196 Collections.reverse(shrinkedImagesWidths); 197 198 shrinkedImages.put(new Integer(shrinked.getWidth()), shrinked); 199 200 return shrinked; 201 } catch (CommandNotAvailable | CommandException e) { 202 return originalPictureInfos; 203 } 204 } 205 } 206 } else 207 return originalPictureInfos; 208 } else 209 return originalPictureInfos; 210 } 211 212 public ImageInfo getOriginalPictureInfos() { 213 updateAccessTime(); 214 return originalPictureInfos; 215 } 216 217 public void cleanUp() { 218 // original picture 219 File orgFile = new File(originalPictureInfos.getFilePath()); 220 if (orgFile.exists()) 221 orgFile.delete(); 222 223 // shrinked ones 224 for (Integer s : shrinkedImages.keySet()) { 225 File skFile = new File(shrinkedImages.get(s).getFilePath()); 226 if (skFile.exists()) 227 skFile.delete(); 228 } 229 230 // tiles 231 for (String tileDef : tilesSet.keySet()) { 232 PictureTiles tiles = tilesSet.get(tileDef); 233 File tileDir = new File(tiles.getTilesPath()); 234 if (tileDir.exists()) { 235 for (File tileFile : tileDir.listFiles()) { 236 tileFile.delete(); 237 } 238 } 239 } 240 } 241 242 public void partialCleanUp(long targetDeltaInKB) { 243 long deletedKB = 0; 244 // tiles 245 for (String tileDef : tilesSet.keySet()) { 246 PictureTiles tiles = tilesSet.get(tileDef); 247 File tileDir = new File(tiles.getTilesPath()); 248 if (tileDir.exists()) { 249 for (File tileFile : tileDir.listFiles()) { 250 deletedKB += tileFile.length() / 1000; 251 tileFile.delete(); 252 if (deletedKB > targetDeltaInKB) 253 return; 254 } 255 } 256 } 257 258 // shrinked ones 259 for (Integer s : shrinkedImages.keySet()) { 260 File skFile = new File(shrinkedImages.get(s).getFilePath()); 261 if (skFile.exists()) { 262 deletedKB += skFile.length() / 1000; 263 skFile.delete(); 264 if (deletedKB > targetDeltaInKB) 265 return; 266 } 267 } 268 269 // original picture 270 File orgFile = new File(originalPictureInfos.getFilePath()); 271 if (orgFile.exists()) 272 orgFile.delete(); 273 274 } 275 276}