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