001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.config; 019 020import java.io.File; 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.Date; 027import java.util.List; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030 031import org.syncany.database.MultiChunkEntry.MultiChunkId; 032 033/** 034 * The cache class represents the local disk cache. It is used for storing multichunks 035 * or other metadata files before upload, and as a download location for the same 036 * files. 037 * 038 * <p>The cache implements an LRU strategy based on the last modified date of the 039 * cached files. When files are accessed using the respective getters, the last modified 040 * date is updated. Using the {@link #clear()}/{@link #clear(long)} method, the cache 041 * can be cleaned. 042 * 043 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 044 */ 045public class Cache { 046 private static final Logger logger = Logger.getLogger(Cache.class.getSimpleName()); 047 048 private static long DEFAULT_CACHE_KEEP_BYTES = 500*1024*1024; 049 private static String FILE_FORMAT_MULTICHUNK_ENCRYPTED = "multichunk-%s"; 050 private static String FILE_FORMAT_MULTICHUNK_DECRYPTED = "multichunk-%s-decrypted"; 051 private static String FILE_FORMAT_DATABASE_FILE_ENCRYPTED = "%s"; 052 053 private long keepBytes; 054 private File cacheDir; 055 056 public Cache(File cacheDir) { 057 this.cacheDir = cacheDir; 058 this.keepBytes = DEFAULT_CACHE_KEEP_BYTES; 059 } 060 061 /** 062 * Returns a file path of a decrypted multichunk file, 063 * given the identifier of a multichunk. 064 */ 065 public File getDecryptedMultiChunkFile(MultiChunkId multiChunkId) { 066 return getFileInCache(FILE_FORMAT_MULTICHUNK_DECRYPTED, multiChunkId.toString()); 067 } 068 069 /** 070 * Returns a file path of a encrypted multichunk file, 071 * given the identifier of a multichunk. 072 */ 073 public File getEncryptedMultiChunkFile(MultiChunkId multiChunkId) { 074 return getFileInCache(FILE_FORMAT_MULTICHUNK_ENCRYPTED, multiChunkId.toString()); 075 } 076 077 /** 078 * Returns a file path of a database remote file. 079 */ 080 public File getDatabaseFile(String name) { // TODO [low] This should be a database file or another key 081 return getFileInCache(FILE_FORMAT_DATABASE_FILE_ENCRYPTED, name); 082 } 083 084 public long getKeepBytes() { 085 return keepBytes; 086 } 087 088 public void setKeepBytes(long keepBytes) { 089 this.keepBytes = keepBytes; 090 } 091 092 /** 093 * Deletes files in the the cache directory using a LRU-strategy until <code>keepBytes</code> 094 * bytes are left. This method calls {@link #clear(long)} using the <code>keepBytes</code> 095 * property. 096 * 097 * <p>This method should not be run while an operation is executed. 098 */ 099 public void clear() { 100 clear(keepBytes); 101 } 102 103 /** 104 * Deletes files in the the cache directory using a LRU-strategy until <code>keepBytes</code> 105 * bytes are left. 106 * 107 * <p>This method should not be run while an operation is executed. 108 */ 109 public void clear(long keepBytes) { 110 List<File> cacheFiles = getSortedFileList(); 111 112 // Determine total size 113 long totalSize = 0; 114 115 for (File cacheFile : cacheFiles) { 116 totalSize += cacheFile.length(); 117 } 118 119 // Delete until total cache size <= keep size 120 if (totalSize > keepBytes) { 121 logger.log(Level.INFO, "Cache too large (" + (totalSize/1024) + " KB), deleting until <= " + (keepBytes/1024/1024) + " MB ..."); 122 123 while (totalSize > keepBytes && cacheFiles.size() > 0) { 124 File eldestCacheFile = cacheFiles.remove(0); 125 126 long fileSize = eldestCacheFile.length(); 127 long fileLastModified = eldestCacheFile.lastModified(); 128 129 logger.log(Level.INFO, "- Deleting from cache (" + new Date(fileLastModified) + ", " + (fileSize/1024) + " KB): " + eldestCacheFile.getName()); 130 131 totalSize -= fileSize; 132 eldestCacheFile.delete(); 133 } 134 } 135 else { 136 logger.log(Level.INFO, "Cache size okay (" + (totalSize/1024) + " KB), no need to clean (keep size is " + (keepBytes/1024/1024) + " MB)"); 137 } 138 } 139 140 /** 141 * Creates temporary file in the local directory cache, typically located at 142 * .syncany/cache. If not deleted by the application, the returned file is automatically 143 * deleted on exit by the JVM. 144 * 145 * @return Temporary file in local directory cache 146 */ 147 public File createTempFile(String name) throws IOException { 148 File tempFile = File.createTempFile(String.format("temp-%s-", name), ".tmp", cacheDir); 149 tempFile.deleteOnExit(); 150 151 return tempFile; 152 } 153 154 /** 155 * Returns the file using the given format and parameters, and 156 * updates the last modified date of the file (used for LRU strategy). 157 */ 158 private File getFileInCache(String format, Object... params) { 159 File fileInCache = new File(cacheDir.getAbsoluteFile(), String.format(format, params)); 160 161 if (fileInCache.exists()) { 162 touchFile(fileInCache); 163 } 164 165 return fileInCache; 166 } 167 168 /** 169 * Sets the last modified date of the given file to the current date/time. 170 */ 171 private void touchFile(File fileInCache) { 172 fileInCache.setLastModified(System.currentTimeMillis()); 173 } 174 175 /** 176 * Returns a list of all files in the cache, sorted by the last modified 177 * date -- eldest first. 178 */ 179 private List<File> getSortedFileList() { 180 File[] cacheFilesList = cacheDir.listFiles(); 181 List<File> sortedCacheFiles = new ArrayList<File>(); 182 183 if (cacheFilesList != null) { 184 sortedCacheFiles.addAll(Arrays.asList(cacheFilesList)); 185 186 Collections.sort(sortedCacheFiles, new Comparator<File>() { 187 @Override 188 public int compare(File file1, File file2) { 189 return Long.compare(file1.lastModified(), file2.lastModified()); 190 } 191 }); 192 } 193 194 return sortedCacheFiles; 195 } 196}