Version 2.2.2 (manually delete from cache)
[fanfix.git] / src / be / nikiroo / utils / Cache.java
CommitLineData
8816d2f7
NR
1package be.nikiroo.utils;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileNotFoundException;
6import java.io.IOException;
7import java.io.InputStream;
8import java.net.URL;
9import java.util.Date;
10
11/**
12 * A generic cache system, with special support for {@link URL}s.
13 * <p>
14 * This cache also manages timeout information.
15 *
16 * @author niki
17 */
18public class Cache {
19 private File dir;
20 private long tooOldChanging;
21 private long tooOldStable;
22
23 /**
24 * Create a new {@link Cache} object.
25 *
26 * @param dir
27 * the directory to use as cache
28 * @param hoursChanging
29 * the number of hours after which a cached file that is thought
30 * to change ~often is considered too old (or -1 for
31 * "never too old")
32 * @param hoursStable
33 * the number of hours after which a cached file that is thought
34 * to change rarely is considered too old (or -1 for
35 * "never too old")
36 *
37 * @throws IOException
38 * in case of I/O error
39 */
40 public Cache(File dir, int hoursChanging, int hoursStable)
41 throws IOException {
42 this.dir = dir;
43 this.tooOldChanging = 1000 * 60 * 60 * hoursChanging;
44 this.tooOldStable = 1000 * 60 * 60 * hoursStable;
45
46 if (dir != null && !dir.exists()) {
47 dir.mkdirs();
48 }
49
50 if (dir == null || !dir.exists()) {
51 throw new IOException("Cannot create the cache directory: "
52 + (dir == null ? "null" : dir.getAbsolutePath()));
53 }
54 }
55
56 /**
57 * Check the resource to see if it is in the cache.
58 *
59 * @param url
60 * the resource to check
61 * @param allowTooOld
62 * allow files even if they are considered too old
63 * @param stable
64 * a stable file (that dones't change too often) -- parameter
65 * used to check if the file is too old to keep or not
66 *
67 * @return TRUE if it is
68 *
69 */
70 public boolean check(URL url, boolean allowTooOld, boolean stable) {
71 File file = getCached(url);
72 if (file.exists()) {
73 if (allowTooOld || !isOld(file, stable)) {
74 return true;
75 }
76 }
77
78 return false;
79 }
80
81 /**
82 * Clean the cache (delete the cached items).
83 *
84 * @param onlyOld
85 * only clean the files that are considered too old for a stable
86 * resource
87 *
88 * @return the number of cleaned items
89 */
90 public int clean(boolean onlyOld) {
91 return clean(onlyOld, dir);
92 }
93
94 /**
95 * Trace information (info/error) generated by this class.
96 * <p>
97 * You can override it if you don't want the default sysout/syserr.
98 *
99 * @param message
100 * the message
101 * @param error
102 * TRUE for error messages, FALSE for information messages
103 */
104 protected void trace(String message, boolean error) {
105 if (error) {
106 System.err.println(message);
107 } else {
108 System.out.println(message);
109 }
110 }
111
112 /**
113 * Clean the cache (delete the cached items) in the given cache directory.
114 *
115 * @param onlyOld
116 * only clean the files that are considered too old for stable
117 * resources
118 * @param cacheDir
119 * the cache directory to clean
120 *
121 * @return the number of cleaned items
122 */
123 private int clean(boolean onlyOld, File cacheDir) {
124 int num = 0;
125 for (File file : cacheDir.listFiles()) {
126 if (file.isDirectory()) {
127 num += clean(onlyOld, file);
128 } else {
129 if (!onlyOld || isOld(file, true)) {
130 if (file.delete()) {
131 num++;
132 } else {
133 trace("Cannot delete temporary file: "
134 + file.getAbsolutePath(), true);
135 }
136 }
137 }
138 }
139
140 return num;
141 }
142
143 /**
144 * Open a resource from the cache if it exists.
145 *
146 * @param uniqueID
147 * the unique ID
148 * @param allowTooOld
149 * allow files even if they are considered too old
150 * @param stable
151 * a stable file (that dones't change too often) -- parameter
152 * used to check if the file is too old to keep or not
153 *
154 * @return the opened resource if found, NULL if not
8816d2f7
NR
155 */
156 public InputStream load(String uniqueID, boolean allowTooOld, boolean stable) {
157 return load(getCached(uniqueID), allowTooOld, stable);
158 }
159
160 /**
161 * Open a resource from the cache if it exists.
162 *
163 * @param url
164 * the resource to open
165 * @param allowTooOld
166 * allow files even if they are considered too old
167 * @param stable
168 * a stable file (that dones't change too often) -- parameter
169 * used to check if the file is too old to keep or not
170 *
171 * @return the opened resource if found, NULL if not
8816d2f7 172 */
2ee6c205 173 public InputStream load(URL url, boolean allowTooOld, boolean stable) {
8816d2f7
NR
174 return load(getCached(url), allowTooOld, stable);
175 }
176
177 /**
178 * Open a resource from the cache if it exists.
179 *
2ee6c205 180 * @param cached
8816d2f7
NR
181 * the resource to open
182 * @param allowTooOld
183 * allow files even if they are considered too old
184 * @param stable
185 * a stable file (that dones't change too often) -- parameter
186 * used to check if the file is too old to keep or not
187 *
188 * @return the opened resource if found, NULL if not
8816d2f7
NR
189 */
190 private InputStream load(File cached, boolean allowTooOld, boolean stable) {
191 if (cached.exists() && (allowTooOld || !isOld(cached, stable))) {
192 try {
193 return new MarkableFileInputStream(new FileInputStream(cached));
194 } catch (FileNotFoundException e) {
195 return null;
196 }
197 }
198
199 return null;
200 }
201
202 /**
203 * Save the given resource to the cache.
204 *
205 * @param in
206 * the input data
207 * @param uniqueID
208 * a unique ID used to locate the cached resource
209 *
210 * @return the resulting {@link File}
211 *
212 * @throws IOException
213 * in case of I/O error
214 */
215 public File save(InputStream in, String uniqueID) throws IOException {
216 File cached = getCached(uniqueID);
217 cached.getParentFile().mkdirs();
218 return save(in, cached);
219 }
220
221 /**
222 * Save the given resource to the cache.
223 *
224 * @param in
225 * the input data
226 * @param url
227 * the {@link URL} used to locate the cached resource
228 *
2ee6c205
NR
229 * @return the actual cache file
230 *
8816d2f7
NR
231 * @throws IOException
232 * in case of I/O error
233 */
234 public File save(InputStream in, URL url) throws IOException {
235 File cached = getCached(url);
236 return save(in, cached);
237 }
238
239 /**
240 * Save the given resource to the cache.
241 *
242 * @param in
243 * the input data
244 * @param cached
245 * the cached {@link File} to save to
246 *
2ee6c205
NR
247 * @return the actual cache file
248 *
8816d2f7
NR
249 * @throws IOException
250 * in case of I/O error
251 */
252 private File save(InputStream in, File cached) throws IOException {
253 IOUtils.write(in, cached);
254 return cached;
255 }
256
2ee6c205
NR
257 /**
258 * Remove the given resource from the cache.
259 *
260 * @param uniqueID
261 * a unique ID used to locate the cached resource
262 *
263 * @return TRUE if it was removed
264 */
265 public boolean remove(String uniqueID) {
266 File cached = getCached(uniqueID);
267 return cached.delete();
268 }
269
270 /**
271 * Remove the given resource from the cache.
272 *
273 * @param url
274 * the {@link URL} used to locate the cached resource
275 *
276 * @return TRUE if it was removed
277 */
278 public boolean remove(URL url) {
279 File cached = getCached(url);
280 return cached.delete();
281 }
282
8816d2f7
NR
283 /**
284 * Check if the {@link File} is too old according to
285 * {@link Cache#tooOldChanging}.
286 *
287 * @param file
288 * the file to check
289 * @param stable
290 * TRUE to denote stable files, that are not supposed to change
291 * too often
292 *
293 * @return TRUE if it is
294 */
295 private boolean isOld(File file, boolean stable) {
296 long max = tooOldChanging;
297 if (stable) {
298 max = tooOldStable;
299 }
300
301 if (max < 0) {
302 return false;
303 }
304
305 long time = new Date().getTime() - file.lastModified();
306 if (time < 0) {
307 trace("Timestamp in the future for file: " + file.getAbsolutePath(),
308 true);
309 }
310
311 return time < 0 || time > max;
312 }
313
314 /**
315 * Return the associated cache {@link File} from this {@link URL}.
316 *
317 * @param url
318 * the {@link URL}
319 *
320 * @return the cached {@link File} version of this {@link URL}
321 */
322 private File getCached(URL url) {
323 File subdir;
324
325 String name = url.getHost();
326 if (name == null || name.isEmpty()) {
327 // File
328 File file = new File(url.getFile());
329 subdir = new File(file.getParent().replace("..", "__"));
330 subdir = new File(dir, allowedChars(subdir.getPath()));
331 name = allowedChars(url.getFile());
332 } else {
333 // URL
334 File subsubDir = new File(dir, allowedChars(url.getHost()));
335 subdir = new File(subsubDir, "_" + allowedChars(url.getPath()));
336 name = allowedChars("_" + url.getQuery());
337 }
338
339 File cacheFile = new File(subdir, name);
340 subdir.mkdirs();
341
342 return cacheFile;
343 }
344
345 /**
346 * Get the basic cache resource file corresponding to this unique ID.
347 * <p>
348 * Note that you may need to add a sub-directory in some cases.
349 *
350 * @param uniqueID
351 * the id
352 *
353 * @return the cached version if present, NULL if not
354 */
355 private File getCached(String uniqueID) {
356 File file = new File(dir, allowedChars(uniqueID));
357 File subdir = new File(file.getParentFile(), "_");
358 return new File(subdir, file.getName());
359 }
360
361 /**
362 * Replace not allowed chars (in a {@link File}) by "_".
363 *
364 * @param raw
365 * the raw {@link String}
366 *
367 * @return the sanitised {@link String}
368 */
369 private String allowedChars(String raw) {
370 return raw.replace('/', '_').replace(':', '_').replace("\\", "_");
371 }
372}