/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwindx.jgt;
import gov.nasa.worldwind.util.Logging;
import java.io.*;
import java.util.*;
/**
* @author tag
* @version $Id: FileStoreDataSet.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class FileStoreDataSet
{
public static final String HOUR = "gov.nasa.worldwindx.examples.util.cachecleaner.HOUR";
public static final String DAY = "gov.nasa.worldwindx.examples.util.cachecleaner.DAY";
public static final String WEEK = "gov.nasa.worldwindx.examples.util.cachecleaner.WEEK";
public static final String MONTH = "gov.nasa.worldwindx.examples.util.cachecleaner.MONTH";
public static final String YEAR = "gov.nasa.worldwindx.examples.util.cachecleaner.YEAR";
protected static class LeafInfo
{
long lastUsed;
long size;
}
protected final File root;
protected final String cacheRootPath;
protected boolean fileGranularity = false; // delete only files individually out of date or all files in a directory
protected ArrayList<File> exclusionList = new ArrayList<File>();
protected ArrayList<LeafInfo> leafDirs = new ArrayList<LeafInfo>();
protected LeafInfo[] sortedLeafDirs;
public FileStoreDataSet(File root, String cacheRootPath)
{
if (root == null)
{
String message = Logging.getMessage("nullValue.FileStorePathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.root = root;
this.cacheRootPath = cacheRootPath;
this.update();
}
protected void update()
{
this.leafDirs.clear();
findLeaves(this.root, this.leafDirs);
if (this.leafDirs.size() == 0)
return;
this.sortedLeafDirs = new LeafInfo[this.leafDirs.size()];
this.sortedLeafDirs = this.leafDirs.toArray(this.sortedLeafDirs);
Arrays.sort(this.sortedLeafDirs, new Comparator<LeafInfo>()
{
public int compare(LeafInfo leafA, LeafInfo leafB)
{
return leafA.lastUsed < leafB.lastUsed ? -1 : leafA.lastUsed == leafB.lastUsed ? 0 : 1;
}
});
}
public boolean isFileGranularity()
{
return fileGranularity;
}
public void setFileGranularity(boolean fileGranularity)
{
this.fileGranularity = fileGranularity;
}
public String getPath()
{
return root.getPath();
}
public String getName()
{
String name = this.cacheRootPath == null ? this.getPath() : this.getPath().replace(
this.cacheRootPath.subSequence(0, this.cacheRootPath.length()), "".subSequence(0, 0));
return name.startsWith("/") ? name.substring(1) : name;
}
public List<File> getExclusions()
{
return Collections.unmodifiableList(this.exclusionList);
}
public void setExclusions(Iterable<? extends File> exclusions)
{
this.exclusionList.clear();
if (exclusions != null)
{
for (File file : exclusions)
{
this.exclusionList.add(file);
}
}
}
public long getSize()
{
long size = 0;
for (LeafInfo leaf : this.leafDirs)
{
size += leaf.size;
}
return size;
}
public long getOutOfScopeSize(String unit, int interval)
{
if (unit == null)
{
String message = Logging.getMessage("nullValue.TimeUnit");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
long previousTime = computeTimeOffset(unit, interval);
long size = 0;
for (LeafInfo leaf : this.sortedLeafDirs)
{
if (leaf.lastUsed > previousTime)
break;
size += leaf.size;
}
return size;
}
public long getLastModified()
{
return this.sortedLeafDirs[this.sortedLeafDirs.length - 1].lastUsed;
}
public void deleteOutOfScopeFiles(String unit, int interval, boolean echo)
{
if (unit == null)
{
String message = Logging.getMessage("nullValue.TimeUnit");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.deleteFiles(this.root, this.exclusionList, computeTimeOffset(unit, interval), echo);
this.update();
}
@SuppressWarnings( {"ResultOfMethodCallIgnored"})
protected void deleteFiles(File dir, List<File> exclusions, long timeBoundary, boolean echo)
{
if (!dir.isDirectory())
return;
boolean directoryOutOfDate = dir.lastModified() < timeBoundary;
File[] files = dir.listFiles();
for (File file : files)
{
// If the file is in the list of excluded files, then ignore it. If the file is a directory its
// descendants will also be ignored.
if (exclusions.contains(file))
continue;
if (file.isFile())
{
if (this.isFileGranularity())
{
if (file.lastModified() < timeBoundary)
{
file.delete();
if (echo)
System.out.println("Deleting FILE: " + file.getPath());
}
}
else if (directoryOutOfDate)
{
file.delete();
if (echo)
System.out.println("Deleting FILE: " + file.getPath());
}
}
else if (file.isDirectory())
{
this.deleteFiles(file, exclusions, timeBoundary, echo); // recurse until files are encountered
if (file.list().length == 0)
{
file.delete();
if (echo)
System.out.println("Deleting DIRECTORY: " + file.getPath());
}
}
}
}
@SuppressWarnings( {"ResultOfMethodCallIgnored"})
public void delete(boolean echo)
{
deleteOutOfScopeFiles(HOUR, 0, echo);
File[] files = this.root.listFiles();
if (files.length == 0)
this.root.delete();
}
protected static long computeTimeOffset(String unit, int interval)
{
GregorianCalendar cal = new GregorianCalendar();
if (interval == 0)
{
}
else if (unit.equals(HOUR))
{
cal.add(Calendar.HOUR, -interval);
}
else if (unit.equals(DAY))
{
cal.add(Calendar.DAY_OF_YEAR, -interval);
}
else if (unit.equals(WEEK))
{
cal.add(Calendar.WEEK_OF_YEAR, -interval);
}
else if (unit.equals(MONTH))
{
cal.add(Calendar.MONTH, -interval);
}
else if (unit.equals(YEAR))
{
cal.add(Calendar.YEAR, -interval);
}
return cal.getTimeInMillis();
}
protected static void findLeaves(File dir, ArrayList<LeafInfo> leaves)
{
if (!dir.isDirectory())
return;
File[] subDirs = dir.listFiles(new FileFilter()
{
public boolean accept(File file)
{
return file.isDirectory();
}
});
if (subDirs.length == 0)
{
LeafInfo li = new LeafInfo();
li.lastUsed = dir.lastModified();
li.size = computeDirectorySize(dir);
leaves.add(li);
}
else
{
for (File subDir : subDirs)
{
findLeaves(subDir, leaves);
}
}
}
protected static long computeDirectorySize(File dir)
{
long size = 0;
File[] files = dir.listFiles();
for (File file : files)
{
try
{
FileInputStream fis = new FileInputStream(file);
size += fis.available();
fis.close();
}
catch (IOException e)
{
String message = Logging.getMessage("generic.ExceptionWhileComputingSize", file.getAbsolutePath());
Logging.logger().fine(message);
}
}
return size;
}
/**
* Find all of the data set directories in a cache root.
*
* @param cacheRoot Cache root to search.
*
* @return List of data sets in the specified cache.
*/
public static List<FileStoreDataSet> getDataSets(File cacheRoot)
{
if (cacheRoot == null)
{
String message = Logging.getMessage("nullValue.FileStorePathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
ArrayList<FileStoreDataSet> datasets = new ArrayList<FileStoreDataSet>();
File[] cacheDirs = FileStoreDataSet.listDirs(cacheRoot);
for (File cacheDir : cacheDirs)
{
if (cacheDir.getName().equals("license"))
continue;
File[] subDirs = FileStoreDataSet.listDirs(cacheDir);
if (subDirs.length == 0)
{
datasets.add(new FileStoreDataSet(cacheDir, cacheRoot.getPath()));
}
else
{
// If the directory should be treated as a single dataset, add just one entry to the list.
if (isSingleDataSet(subDirs))
{
datasets.add(new FileStoreDataSet(cacheDir, cacheRoot.getPath()));
}
// Otherwise add each subdirectory as a separate data set.
else
{
for (File sd : subDirs)
{
FileStoreDataSet ds = new FileStoreDataSet(sd, cacheRoot.getPath());
datasets.add(ds);
}
}
}
}
return datasets;
}
/**
* List all of the sub-directories in a parent directory.
*
* @param parent Parent directory to search.
*
* @return All sub-directories under {@code parent}.
*/
protected static File[] listDirs(File parent)
{
return parent.listFiles(new FileFilter()
{
public boolean accept(File file)
{
return file.isDirectory();
}
});
}
/**
* Determines if a list of sub-directories should be treated as a single data set. This implementation returns
* {@code true} if all of the sub-directories have numeric names. In this case, the numeric directories are most
* likely used by the cache implementation to group files in a single data set. The numeric directory names do not
* provide meaningful grouping to the user.
*
* @param subDirs List of sub-directories to test.
*
* @return {@code true} if the directories should be treated as a single data set.
*/
protected static boolean isSingleDataSet(File[] subDirs)
{
boolean onlyNumericDirs = true;
for (File sd : subDirs)
{
if (!isNumeric(sd.getName()))
onlyNumericDirs = false;
}
return onlyNumericDirs;
}
/**
* Determines if a string contains only digits.
*
* @param s String to test.
*
* @return {@code true} if {@code s} contains only digits.
*/
protected static boolean isNumeric(String s)
{
for (char c : s.toCharArray())
{
if (!Character.isDigit(c))
return false;
}
return true;
}
}