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