39cd23e44a6a07aad20cd150c6e579f20b98cbcd
[jalview.git] / src / jalview / io / BackupFiles.java
1 package jalview.io;
2
3 import jalview.bin.Cache;
4
5 import java.io.File;
6 import java.io.IOException;
7 import java.util.Arrays;
8 import java.util.HashMap;
9 import java.util.TreeMap;
10
11 /*
12  * BackupFiles used for manipulating (naming rolling/deleting) backup/version files when an alignment or project file is saved.
13  * User configurable options are:
14  * BACKUPFILES_ENABLED - boolean flag as to whether to use this mechanism or act as before, including overwriting files as saved.
15  * BACKUPFILES_SUFFIX - a template to insert just before the file extension.  Use '%n' to be replaced by a 0-led SUFFIX_DIGITS long integer.
16  * BACKUPFILES_ROLL_MAX - the maximum number of backupfiles to keep for any one alignment or project file.
17  * BACKUPFILES_SUFFIX_DIGITS - the number of digits to insert replace %n with (e.g. BACKUPFILES_SUFFIX_DIGITS = 3 would make "001", "002", etc)
18  * 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. 
19  */
20
21 public class BackupFiles
22 {
23
24   // labels for saved params in Cache and .jalview_properties
25   private static String NS = "BACKUPFILES";
26
27   public static String ENABLED = NS + "_ENABLED";
28
29   public static String SUFFIX = NS + "_SUFFIX";
30
31   public static String ROLL_MAX = NS + "_ROLL_MAX";
32
33   public static String SUFFIX_DIGITS = NS + "_SUFFIX_DIGITS";
34
35   protected static String NUM_PLACEHOLDER = "%n";
36
37   public static String REVERSE_ORDER = NS + "_REVERSE_ORDER";
38
39   private static String DEFAULT_TEMP_FILE = "jalview_temp_file_" + NS;
40
41   // file - File object to be backed up and then updated (written over)
42   private File file;
43
44   // enabled - default flag as to whether to do the backup file roll (if not
45   // defined in preferences)
46   private boolean enabled;
47
48   // defaultSuffix - default template to use to append to basename of file
49   private String suffix;
50
51   // defaultMax - default max number of backup files
52   private int max;
53
54   // defaultDigits - number of zero-led digits to use in the filename
55   private int digits;
56
57   // reverseOrder - set to true to make newest (latest) files lowest number
58   // (like rolled log files)
59   private boolean reverseOrder;
60
61   // temp saved file to become new saved file
62   private File tempFile;
63
64   public BackupFiles(String filename)
65   {
66     this(new File(filename));
67   }
68
69   // first time defaults for ENABLED, SUFFIX, ROLL_MAX, SUFFIX_DIGITS and
70   // REVERSE_ORDER
71   public BackupFiles(File file)
72   {
73     this(file, true, "-v" + NUM_PLACEHOLDER, 4, 3, false);
74   }
75
76   // set, get and rename temp file into place
77   public void setTempFile(File temp)
78   {
79     this.tempFile = temp;
80   }
81
82   public File getTempFile()
83   {
84     return tempFile;
85   }
86
87   public boolean renameTempFile()
88   {
89     return tempFile.renameTo(file);
90   }
91
92   public BackupFiles(File file, boolean defaultEnabled,
93           String defaultSuffix,
94           int defaultMax, int defaultDigits, boolean defaultReverseOrder)
95   {
96     this.file = file;
97     this.enabled = Cache.getDefault(ENABLED, defaultEnabled);
98     this.suffix = Cache.getDefault(SUFFIX, defaultSuffix);
99     this.max = Cache.getDefault(ROLL_MAX, defaultMax);
100     this.digits = Cache.getDefault(SUFFIX_DIGITS, defaultDigits);
101     this.reverseOrder = Cache.getDefault(REVERSE_ORDER,
102             defaultReverseOrder);
103     
104     // create a temp file to save new data in
105     File temp;
106     try
107     {
108       if (file != null)
109       {
110         String tempfilename = file.getName();
111         File tempdir = file.getParentFile();
112         temp = File.createTempFile(tempfilename, ".tmp", tempdir);
113       }
114       else
115       {
116         temp = File.createTempFile(DEFAULT_TEMP_FILE, ".tmp");
117         setTempFile(temp);
118       }
119     } catch (IOException e)
120     {
121       System.out.println(
122               "Could not create temp file to save into (IOException)");
123     } catch (Exception e)
124     {
125       System.out.println("Exception ctreating temp file for saving");
126     }
127
128   }
129
130   // roll the backupfiles
131   public boolean rollBackupFiles()
132   {
133
134     // file doesn't yet exist or backups are not enabled
135     if ((!file.exists()) || (!enabled) || (max < 1))
136     {
137       // nothing to do
138       return true;
139     }
140
141     // split filename up to insert suffix template in the right place. template
142     // and backupMax can be set in .jalview_properties
143     String dir = "";
144     File dirFile;
145     try
146     {
147       dirFile = file.getParentFile();
148       dir = dirFile.getCanonicalPath();
149     } catch (Exception e)
150     {
151       System.out.println(
152               "Could not get canonical path for file '" + file + "'");
153       return false;
154     }
155     String filename = file.getName();
156     String basename = filename;
157     String extension = "";
158     int dotcharpos = filename.lastIndexOf('.');
159     // don't split of filenames with the last '.' at the very beginning or
160     // very end of the filename
161     if ((dotcharpos > 0) && (dotcharpos < filename.length() - 1))
162     {
163       basename = filename.substring(0, dotcharpos);
164       extension = filename.substring(dotcharpos); // NOTE this includes the '.'
165     }
166
167     boolean ret = true;
168     // Create/move backups up one
169     String numString = null;
170     File lastfile = null;
171
172     if (reverseOrder)
173     {
174       // backup style numbering
175
176       int tempMax = max;
177       // max == -1 means no limits
178       if (max == -1)
179       {
180         // do something cleverer here (possibly)!
181         tempMax = 10000;
182       }
183
184       for (int m = 0; m < tempMax; m++)
185       {
186         int n = tempMax - m;
187         String backupfilename = dir + File.separatorChar
188                 + BackupFilenameParts.getBackupFilename(n, basename, suffix,
189                         digits, extension);
190         File backupfile_n = new File(backupfilename);
191
192         if (!backupfile_n.exists())
193         {
194           lastfile = backupfile_n;
195           continue;
196         }
197
198         if (m == 0)
199         { // Move the max backup to /tmp instead of deleting (Just In
200           // Case)
201           String tmpfile = "tmp-" + backupfile_n.getName();
202           try
203           {
204             File tmpFile = File.createTempFile(tmpfile, ".tmp");
205             ret = ret && backupfile_n.renameTo(tmpFile);
206           } catch (IOException e)
207           {
208             System.out.println(
209                     "Could not create temp file '" + tmpfile + ".tmp'");
210           }
211         }
212         else
213         {
214           // Just In Case
215           if (lastfile != null)
216           {
217             ret = ret && backupfile_n.renameTo(lastfile);
218           }
219         }
220
221         lastfile = backupfile_n;
222       }
223
224       // now actually backup the important file!
225       ret = ret && file.renameTo(lastfile);
226     }
227     else
228     {
229       // version style numbering (with earliest file deletion if max files
230       // reached)
231
232       // find existing backup files
233       BackupFilenameFilter bff = new BackupFilenameFilter(basename, suffix,
234               digits,
235               extension);
236       File[] backupFiles = dirFile.listFiles(bff);
237       int nextIndexNum;
238       
239       if (backupFiles.length == 0)
240       {
241         nextIndexNum = 1;
242       } else {
243
244         // and sort them (based on integer found in the suffix) using a
245         // precomputed Hashmap for speed
246         HashMap bfHashMap = new HashMap<Integer, File>();
247         for (int i = 0; i < backupFiles.length; i++)
248         {
249           File f = backupFiles[i];
250           BackupFilenameParts bfp = new BackupFilenameParts(f, basename, suffix, digits, extension);
251           bfHashMap.put(bfp.indexNum(), f);
252         }
253         TreeMap<Integer, File> bfTreeMap = new TreeMap<>();
254         bfTreeMap.putAll(bfHashMap);
255
256         bfTreeMap.values().toArray(backupFiles);
257         
258         // max value of -1 means keep all backup files
259         if (bfTreeMap.size() >= max && max != -1)
260         {
261           // need to delete some files to keep number of backups to designated
262           // max
263           int numToDelete = bfTreeMap.size() - max;
264           File[] filesToDelete = Arrays.copyOfRange(backupFiles, 0,
265                   numToDelete - 1);
266
267           /******************************************
268            * CONFIRM THESE DELETIONS WITH THE USER! *
269            ******************************************/
270           for (int i = 0; i < filesToDelete.length; i++)
271           {
272             File toDelete = filesToDelete[i];
273             toDelete.delete();
274           }
275
276         }
277
278         nextIndexNum = bfTreeMap.lastKey() + 1;
279
280         // Let's make the new backup file!! yay, got there at last!
281         String nextBackupFilename = dir + File.separatorChar
282                 + BackupFilenameParts.getBackupFilename(nextIndexNum,
283                         basename, suffix, digits, extension);
284         File nextBackupFile = new File(nextBackupFilename);
285         ret = ret && file.renameTo(nextBackupFile);
286       }
287     }
288
289     return ret;
290   }
291
292 }
293