1 package be
.nikiroo
.utils
;
4 import java
.io
.FileNotFoundException
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
10 import be
.nikiroo
.utils
.streams
.MarkableFileInputStream
;
13 * A generic cache system, with special support for {@link URL}s.
15 * This cache also manages timeout information.
21 private long tooOldChanging
;
22 private long tooOldStable
;
23 private TraceHandler tracer
= new TraceHandler();
26 * Only for inheritance.
32 * Create a new {@link Cache} object.
35 * the directory to use as cache
36 * @param hoursChanging
37 * the number of hours after which a cached file that is thought
38 * to change ~often is considered too old (or -1 for
41 * the number of hours after which a cached file that is thought
42 * to change rarely is considered too old (or -1 for
46 * in case of I/O error
48 public Cache(File dir
, int hoursChanging
, int hoursStable
)
51 this.tooOldChanging
= 1000L * 60 * 60 * hoursChanging
;
52 this.tooOldStable
= 1000L * 60 * 60 * hoursStable
;
54 if (dir
!= null && !dir
.exists()) {
58 if (dir
== null || !dir
.exists()) {
59 throw new IOException("Cannot create the cache directory: "
60 + (dir
== null ?
"null" : dir
.getAbsolutePath()));
65 * The traces handler for this {@link Cache}.
67 * @return the traces handler
69 public TraceHandler
getTraceHandler() {
74 * The traces handler for this {@link Cache}.
77 * the new traces handler
79 public void setTraceHandler(TraceHandler tracer
) {
81 tracer
= new TraceHandler(false, false, false);
88 * Check the resource to see if it is in the cache.
91 * the resource to check
93 * allow files even if they are considered too old
95 * a stable file (that dones't change too often) -- parameter
96 * used to check if the file is too old to keep or not
98 * @return TRUE if it is
101 public boolean check(String uniqueID
, boolean allowTooOld
, boolean stable
) {
102 return check(getCached(uniqueID
), allowTooOld
, stable
);
106 * Check the resource to see if it is in the cache.
109 * the resource to check
111 * allow files even if they are considered too old
113 * a stable file (that dones't change too often) -- parameter
114 * used to check if the file is too old to keep or not
116 * @return TRUE if it is
119 public boolean check(URL url
, boolean allowTooOld
, boolean stable
) {
120 return check(getCached(url
), allowTooOld
, stable
);
124 * Check the resource to see if it is in the cache.
127 * the resource to check
129 * allow files even if they are considered too old
131 * a stable file (that dones't change too often) -- parameter
132 * used to check if the file is too old to keep or not
134 * @return TRUE if it is
137 private boolean check(File cached
, boolean allowTooOld
, boolean stable
) {
138 if (cached
.exists() && cached
.isFile()) {
139 if (!allowTooOld
&& isOld(cached
, stable
)) {
140 if (!cached
.delete()) {
141 tracer
.error("Cannot delete temporary file: "
142 + cached
.getAbsolutePath());
153 * Clean the cache (delete the cached items).
156 * only clean the files that are considered too old for a stable
159 * @return the number of cleaned items
161 public int clean(boolean onlyOld
) {
162 long ms
= System
.currentTimeMillis();
164 tracer
.trace("Cleaning cache from old files...");
166 int num
= clean(onlyOld
, dir
, -1);
168 tracer
.trace(num
+ "cache items cleaned in "
169 + (System
.currentTimeMillis() - ms
) + " ms");
175 * Clean the cache (delete the cached items) in the given cache directory.
178 * only clean the files that are considered too old for stable
181 * the cache directory to clean
183 * stop after limit files deleted, or -1 for unlimited
185 * @return the number of cleaned items
187 private int clean(boolean onlyOld
, File cacheDir
, int limit
) {
189 File
[] files
= cacheDir
.listFiles();
191 for (File file
: files
) {
192 if (limit
>= 0 && num
>= limit
) {
196 if (file
.isDirectory()) {
197 num
+= clean(onlyOld
, file
, limit
);
198 file
.delete(); // only if empty
200 if (!onlyOld
|| isOld(file
, true)) {
204 tracer
.error("Cannot delete temporary file: "
205 + file
.getAbsolutePath());
216 * Open a resource from the cache if it exists.
221 * allow files even if they are considered too old
223 * a stable file (that dones't change too often) -- parameter
224 * used to check if the file is too old to keep or not
226 * @return the opened resource if found, NULL if not
228 public InputStream
load(String uniqueID
, boolean allowTooOld
, boolean stable
) {
229 return load(getCached(uniqueID
), allowTooOld
, stable
);
233 * Open a resource from the cache if it exists.
236 * the resource to open
238 * allow files even if they are considered too old
240 * a stable file (that doesn't change too often) -- parameter
241 * used to check if the file is too old to keep or not in the
244 * @return the opened resource if found, NULL if not
246 public InputStream
load(URL url
, boolean allowTooOld
, boolean stable
) {
247 return load(getCached(url
), allowTooOld
, stable
);
251 * Open a resource from the cache if it exists.
254 * the resource to open
256 * allow files even if they are considered too old
258 * a stable file (that dones't change too often) -- parameter
259 * used to check if the file is too old to keep or not
261 * @return the opened resource if found, NULL if not
263 private InputStream
load(File cached
, boolean allowTooOld
, boolean stable
) {
264 if (cached
.exists() && cached
.isFile()
265 && (allowTooOld
|| !isOld(cached
, stable
))) {
267 return new MarkableFileInputStream(cached
);
268 } catch (FileNotFoundException e
) {
277 * Save the given resource to the cache.
282 * a unique ID used to locate the cached resource
284 * @return the number of bytes written
286 * @throws IOException
287 * in case of I/O error
289 public long save(InputStream in
, String uniqueID
) throws IOException
{
290 File cached
= getCached(uniqueID
);
291 cached
.getParentFile().mkdirs();
292 return save(in
, cached
);
296 * Save the given resource to the cache.
301 * the {@link URL} used to locate the cached resource
303 * @return the number of bytes written
305 * @throws IOException
306 * in case of I/O error
308 public long save(InputStream in
, URL url
) throws IOException
{
309 File cached
= getCached(url
);
310 return save(in
, cached
);
314 * Save the given resource to the cache.
316 * Will also clean the {@link Cache} from old files.
321 * the cached {@link File} to save to
323 * @return the number of bytes written
325 * @throws IOException
326 * in case of I/O error
328 private long save(InputStream in
, File cached
) throws IOException
{
329 // We want to force at least an immediate SAVE/LOAD to work for some
330 // workflows, even if we don't accept cached files (times set to "0"
331 // -- and not "-1" or a positive value)
332 clean(true, dir
, 10);
333 cached
.getParentFile().mkdirs(); // in case we deleted our own parent
334 long bytes
= IOUtils
.write(in
, cached
);
339 * Remove the given resource from the cache.
342 * a unique ID used to locate the cached resource
344 * @return TRUE if it was removed
346 public boolean remove(String uniqueID
) {
347 File cached
= getCached(uniqueID
);
348 return cached
.delete();
352 * Remove the given resource from the cache.
355 * the {@link URL} used to locate the cached resource
357 * @return TRUE if it was removed
359 public boolean remove(URL url
) {
360 File cached
= getCached(url
);
361 return cached
.delete();
365 * Check if the {@link File} is too old according to
366 * {@link Cache#tooOldChanging}.
371 * TRUE to denote stable files, that are not supposed to change
374 * @return TRUE if it is
376 private boolean isOld(File file
, boolean stable
) {
377 long max
= tooOldChanging
;
386 long time
= new Date().getTime() - file
.lastModified();
388 tracer
.error("Timestamp in the future for file: "
389 + file
.getAbsolutePath());
392 return time
< 0 || time
> max
;
396 * Return the associated cache {@link File} from this {@link URL}.
401 * @return the cached {@link File} version of this {@link URL}
403 private File
getCached(URL url
) {
406 String name
= url
.getHost();
407 if (name
== null || name
.isEmpty()) {
409 File file
= new File(url
.getFile());
410 if (file
.getParent() == null) {
411 subdir
= new File("+");
413 subdir
= new File(file
.getParent().replace("..", "__"));
415 subdir
= new File(dir
, allowedChars(subdir
.getPath()));
416 name
= allowedChars(url
.getFile());
419 File subsubDir
= new File(dir
, allowedChars(url
.getHost()));
420 subdir
= new File(subsubDir
, "_" + allowedChars(url
.getPath()));
421 name
= allowedChars("_" + url
.getQuery());
424 File cacheFile
= new File(subdir
, name
);
431 * Get the basic cache resource file corresponding to this unique ID.
433 * Note that you may need to add a sub-directory in some cases.
438 * @return the cached version if present, NULL if not
440 private File
getCached(String uniqueID
) {
441 File file
= new File(dir
, allowedChars(uniqueID
));
442 File subdir
= new File(file
.getParentFile(), "_");
443 return new File(subdir
, file
.getName());
447 * Replace not allowed chars (in a {@link File}) by "_".
450 * the raw {@link String}
452 * @return the sanitised {@link String}
454 private String
allowedChars(String raw
) {
455 return raw
.replace('/', '_').replace(':', '_').replace("\\", "_");