import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/*
+ * BackupFiles used for manipulating (naming rolling/deleting) backup/version files when an alignment or project file is saved.
+ * User configurable options are:
+ * BACKUPFILES_ENABLED - boolean flag as to whether to use this mechanism or act as before, including overwriting files as saved.
+ * BACKUPFILES_SUFFIX - a template to insert just before the file extension. Use '%n' to be replaced by a 0-led SUFFIX_DIGITS long integer.
+ * BACKUPFILES_ROLL_MAX - the maximum number of backupfiles to keep for any one alignment or project file.
+ * BACKUPFILES_SUFFIX_DIGITS - the number of digits to insert replace %n with (e.g. BACKUPFILES_SUFFIX_DIGITS = 3 would make "001", "002", etc)
+ * BACKUPFILES_REVERSE_ORDER - if true then "logfile" style numbering and file rolling will occur. If false then ever-increasing version numbering will occur, but old files will still be deleted if there are more than ROLL_MAX backup files.
+ */
public class BackupFiles
{
public static String SUFFIX_DIGITS = NS + "_SUFFIX_DIGITS";
+ protected static String NUM_PLACEHOLDER = "%n";
+
public static String REVERSE_ORDER = NS + "_REVERSE_ORDER";
private static String DEFAULT_TEMP_FILE = "jalview_temp_file_" + NS;
// enabled - default flag as to whether to do the backup file roll (if not
// defined in preferences)
- private boolean enabled = true;
+ private boolean enabled;
// defaultSuffix - default template to use to append to basename of file
- private String suffix = "-v%n";
+ private String suffix;
// defaultMax - default max number of backup files
- private int max = 4;
+ private int max;
// defaultDigits - number of zero-led digits to use in the filename
- private int digits = 2;
+ private int digits;
// reverseOrder - set to true to make newest (latest) files lowest number
// (like rolled log files)
- private boolean reverseOrder = false;
+ private boolean reverseOrder;
// temp saved file to become new saved file
private File tempFile;
// REVERSE_ORDER
public BackupFiles(File file)
{
- this(file, true, "-v%n", 4, 2, false);
+ this(file, true, "-v" + NUM_PLACEHOLDER, 4, 3, false);
}
// set, get and rename temp file into place
return tempFile.renameTo(file);
}
- protected BackupFiles(File file, boolean defaultEnabled,
+ public BackupFiles(File file, boolean defaultEnabled,
String defaultSuffix,
int defaultMax, int defaultDigits, boolean defaultReverseOrder)
{
// split filename up to insert suffix template in the right place. template
// and backupMax can be set in .jalview_properties
String dir = "";
+ File dirFile;
try
{
- File dirFile = file.getParentFile();
+ dirFile = file.getParentFile();
dir = dirFile.getCanonicalPath();
} catch (Exception e)
{
// Create/move backups up one
String numString = null;
File lastfile = null;
- for (int m = 0; m < max; m++)
+
+ if (reverseOrder)
{
- int n = reverseOrder ? max - m : m + 1;
- numString = String.format("%0" + digits + "d", n);
- String backupSuffix = suffix.replaceAll("%n", numString);
- String backupfilename = dir + File.separatorChar + basename
- + backupSuffix + extension;
- File backupfile_n = new File(backupfilename);
-
- if (!backupfile_n.exists())
+ // backup style numbering
+
+ int tempMax = max;
+ // max == -1 means no limits
+ if (max == -1)
{
- lastfile = backupfile_n;
- continue;
+ // do something cleverer here (possibly)!
+ tempMax = 10000;
}
- if (m == 0)
- { // Move the max backup to /tmp instead of deleting (Just In
- // Case)
- String tmpfile = "tmp-" + backupfile_n.getName();
- try
+ for (int m = 0; m < tempMax; m++)
+ {
+ int n = tempMax - m;
+ String backupfilename = dir + File.separatorChar
+ + BackupFilenameParts.getBackupFilename(n, basename, suffix,
+ digits, extension);
+ File backupfile_n = new File(backupfilename);
+
+ if (!backupfile_n.exists())
{
- File tmpFile = File.createTempFile(tmpfile, ".tmp");
- ret = ret && backupfile_n.renameTo(tmpFile);
- } catch (IOException e)
+ lastfile = backupfile_n;
+ continue;
+ }
+
+ if (m == 0)
+ { // Move the max backup to /tmp instead of deleting (Just In
+ // Case)
+ String tmpfile = "tmp-" + backupfile_n.getName();
+ try
+ {
+ File tmpFile = File.createTempFile(tmpfile, ".tmp");
+ ret = ret && backupfile_n.renameTo(tmpFile);
+ } catch (IOException e)
+ {
+ System.out.println(
+ "Could not create temp file '" + tmpfile + ".tmp'");
+ }
+ }
+ else
{
- System.out.println(
- "Could not create temp file '" + tmpfile + ".tmp'");
+ // Just In Case
+ if (lastfile != null)
+ {
+ ret = ret && backupfile_n.renameTo(lastfile);
+ }
}
+
+ lastfile = backupfile_n;
}
- else
+
+ // now actually backup the important file!
+ ret = ret && file.renameTo(lastfile);
+ }
+ else
+ {
+ // version style numbering (with earliest file deletion if max files
+ // reached)
+
+ // find existing backup files
+ BackupFilenameFilter bff = new BackupFilenameFilter(basename, suffix,
+ digits,
+ extension);
+ File[] backupFiles = dirFile.listFiles(bff);
+ int nextIndexNum;
+
+ if (backupFiles.length == 0)
{
- // Just In Case
- if (lastfile != null)
+ nextIndexNum = 1;
+ } else {
+
+ // and sort them (based on integer found in the suffix) using a
+ // precomputed Hashmap for speed
+ HashMap bfHashMap = new HashMap<Integer, File>();
+ for (int i = 0; i < backupFiles.length; i++)
{
- ret = ret && backupfile_n.renameTo(lastfile);
+ File f = backupFiles[i];
+ BackupFilenameParts bfp = new BackupFilenameParts(f, basename, suffix, digits, extension);
+ bfHashMap.put(bfp.indexNum(), f);
}
- }
+ TreeMap<Integer, File> bfTreeMap = new TreeMap<>();
+ bfTreeMap.putAll(bfHashMap);
- lastfile = backupfile_n;
- }
+ bfTreeMap.values().toArray(backupFiles);
+
+ // max value of -1 means keep all backup files
+ if (bfTreeMap.size() >= max && max != -1)
+ {
+ // need to delete some files to keep number of backups to designated
+ // max
+ int numToDelete = bfTreeMap.size() - max;
+ File[] filesToDelete = Arrays.copyOfRange(backupFiles, 0,
+ numToDelete - 1);
+
+ /******************************************
+ * CONFIRM THESE DELETIONS WITH THE USER! *
+ ******************************************/
+ for (int i = 0; i < filesToDelete.length; i++)
+ {
+ File toDelete = filesToDelete[i];
+ toDelete.delete();
+ }
- // now actually backup the important file!
- ret = ret && file.renameTo(lastfile);
+ }
+
+ nextIndexNum = bfTreeMap.lastKey() + 1;
+
+ // Let's make the new backup file!! yay, got there at last!
+ String nextBackupFilename = dir + File.separatorChar
+ + BackupFilenameParts.getBackupFilename(nextIndexNum,
+ basename, suffix, digits, extension);
+ File nextBackupFile = new File(nextBackupFilename);
+ ret = ret && file.renameTo(nextBackupFile);
+ }
+ }
return ret;
}
}
+