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