JAL-3949 - refactor logging from jalview.bin.Cache to jalview.bin.Console
[jalview.git] / src / jalview / io / BackupFiles.java
index 2939cd9..defc47b 100644 (file)
@@ -1,28 +1,50 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.io;
 
-import jalview.bin.Cache;
-import jalview.gui.Desktop;
-import jalview.gui.JvOptionPane;
-import jalview.util.MessageManager;
-
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.TreeMap;
 
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
 /*
  * 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 after the file extension.  Use '%n' to be replaced by a 0-led SUFFIX_DIGITS long integer.
- * BACKUPFILES_NO_MAX - flag to turn off setting a maximum number of backup files to keep.
- * 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. 
- * BACKUPFILES_CONFIRM_DELETE_OLD - if true then prompt/confirm with the user when deleting older backup/version files.
+ * The rest of the options are now saved as BACKUPFILES_PRESET, BACKUPFILES_SAVED and BACKUPFILES_CUSTOM
+ * (see BackupFilesPresetEntry)
  */
 
 public class BackupFiles
@@ -33,21 +55,8 @@ public class BackupFiles
 
   public static final String ENABLED = NS + "_ENABLED";
 
-  public static final String SUFFIX = NS + "_SUFFIX";
-
-  public static final String NO_MAX = NS + "_NO_MAX";
-
-  public static final String ROLL_MAX = NS + "_ROLL_MAX";
-
-  public static final String SUFFIX_DIGITS = NS + "_SUFFIX_DIGITS";
-
   public static final String NUM_PLACEHOLDER = "%n";
 
-  public static final String REVERSE_ORDER = NS + "_REVERSE_ORDER";
-
-  public static final String CONFIRM_DELETE_OLD = NS
-          + "_CONFIRM_DELETE_OLD";
-
   private static final String DEFAULT_TEMP_FILE = "jalview_temp_file_" + NS;
 
   private static final String TEMP_FILE_EXT = ".tmp";
@@ -86,15 +95,16 @@ public class BackupFiles
   private boolean tempFileWriteSuccess;
 
   // array of files to be deleted, with extra information
-  private ArrayList<DeleteFile> deleteFiles = new ArrayList<>();
-
-  // next backup filename
-  private File nextBackupFile;
+  private ArrayList<File> deleteFiles = new ArrayList<>();
 
   // date formatting for modification times
   private static final SimpleDateFormat sdf = new SimpleDateFormat(
           "yyyy-MM-dd HH:mm:ss");
 
+  private static final String newTempFileSuffix = "_newfile";
+
+  private static final String oldTempFileSuffix = "_oldfile_tobedeleted";
+
   public BackupFiles(String filename)
   {
     this(new File(filename));
@@ -104,20 +114,15 @@ public class BackupFiles
   // REVERSE_ORDER
   public BackupFiles(File file)
   {
-    this(file, ".bak" + NUM_PLACEHOLDER, false, 3, 3, false);
-  }
-
-  public BackupFiles(File file, String defaultSuffix, boolean defaultNoMax,
-          int defaultMax, int defaultDigits, boolean defaultReverseOrder)
-  {
     classInit();
     this.file = file;
-    this.suffix = Cache.getDefault(SUFFIX, defaultSuffix);
-    this.noMax = Cache.getDefault(NO_MAX, defaultNoMax);
-    this.max = Cache.getDefault(ROLL_MAX, defaultMax);
-    this.digits = Cache.getDefault(SUFFIX_DIGITS, defaultDigits);
-    this.reverseOrder = Cache.getDefault(REVERSE_ORDER,
-            defaultReverseOrder);
+    BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
+            .getSavedBackupEntry();
+    this.suffix = bfpe.suffix;
+    this.noMax = bfpe.keepAll;
+    this.max = bfpe.rollMax;
+    this.digits = bfpe.digits;
+    this.reverseOrder = bfpe.reverse;
 
     // create a temp file to save new data in
     File temp = null;
@@ -127,27 +132,47 @@ public class BackupFiles
       {
         String tempfilename = file.getName();
         File tempdir = file.getParentFile();
-        temp = File.createTempFile(tempfilename, TEMP_FILE_EXT, tempdir);
+        Console.trace(
+                "BACKUPFILES [file!=null] attempting to create temp file for "
+                        + tempfilename + " in dir " + tempdir);
+        temp = File.createTempFile(tempfilename,
+                TEMP_FILE_EXT + newTempFileSuffix, tempdir);
+        Console.debug(
+                "BACKUPFILES using temp file " + temp.getAbsolutePath());
       }
       else
       {
+        Console.trace(
+                "BACKUPFILES [file==null] attempting to create default temp file "
+                        + DEFAULT_TEMP_FILE + " with extension "
+                        + TEMP_FILE_EXT);
         temp = File.createTempFile(DEFAULT_TEMP_FILE, TEMP_FILE_EXT);
       }
     } catch (IOException e)
     {
-      System.out.println(
-              "Could not create temp file to save into (IOException)");
+      Console.error("Could not create temp file to save to (IOException)");
+      Console.error(e.getMessage());
+      Console.debug(Cache.getStackTraceString(e));
     } catch (Exception e)
     {
-      System.out.println("Exception ctreating temp file for saving");
+      Console.error("Exception creating temp file for saving");
+      Console.debug(Cache.getStackTraceString(e));
     }
     this.setTempFile(temp);
   }
 
   public static void classInit()
   {
-    setEnabled(Cache.getDefault(ENABLED, true));
-    setConfirmDelete(Cache.getDefault(CONFIRM_DELETE_OLD, true));
+    Console.initLogger();
+    Console.trace("BACKUPFILES classInit");
+    boolean e = Cache.getDefault(ENABLED, !Platform.isJS());
+    setEnabled(e);
+    Console.trace("BACKUPFILES " + (e ? "enabled" : "disabled"));
+    BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
+            .getSavedBackupEntry();
+    Console.trace("BACKUPFILES preset scheme " + bfpe.toString());
+    setConfirmDelete(bfpe.confirmDelete);
+    Console.trace("BACKUPFILES confirm delete " + bfpe.confirmDelete);
   }
 
   public static void setEnabled(boolean flag)
@@ -191,9 +216,9 @@ public class BackupFiles
       path = this.getTempFile().getCanonicalPath();
     } catch (IOException e)
     {
-      System.out.println(
-              "IOException when getting Canonical Path of temp file '"
-                      + this.getTempFile().getName() + "'");
+      Console.error("IOException when getting Canonical Path of temp file '"
+              + this.getTempFile().getName() + "'");
+      Console.debug(Cache.getStackTraceString(e));
     }
     return path;
   }
@@ -212,7 +237,7 @@ public class BackupFiles
 
   public boolean renameTempFile()
   {
-    return tempFile.renameTo(file);
+    return moveFileToFile(tempFile, file);
   }
 
   // roll the backupfiles
@@ -229,31 +254,38 @@ public class BackupFiles
             || suffix.length() == 0)
     {
       // nothing to do
+      Console.debug("BACKUPFILES rollBackupFiles nothing to do." + ", "
+              + "filename: " + (file != null ? file.getName() : "null")
+              + ", " + "file exists: " + file.exists() + ", " + "enabled: "
+              + enabled + ", " + "max: " + max + ", " + "suffix: '" + suffix
+              + "'");
       return true;
     }
 
+    Console.trace("BACKUPFILES rollBackupFiles starting");
+
     String dir = "";
     File dirFile;
     try
     {
       dirFile = file.getParentFile();
       dir = dirFile.getCanonicalPath();
+      Console.trace("BACKUPFILES dir: " + dir);
     } catch (Exception e)
     {
-      System.out.println(
-              "Could not get canonical path for file '" + file + "'");
+      Console.error("Could not get canonical path for file '" + file + "'");
+      Console.error(e.getMessage());
+      Console.debug(Cache.getStackTraceString(e));
       return false;
     }
     String filename = file.getName();
     String basename = filename;
 
+    Console.trace("BACKUPFILES filename is " + filename);
     boolean ret = true;
     // Create/move backups up one
 
     deleteFiles.clear();
-    //File[] oldFilesToDelete = null;
-    //File[] newerOldFilesToDelete = null; // put files with newer modification
-                                         // timestamps in here to warn the user!
 
     // find existing backup files
     BackupFilenameFilter bff = new BackupFilenameFilter(basename, suffix,
@@ -261,9 +293,12 @@ public class BackupFiles
     File[] backupFiles = dirFile.listFiles(bff);
     int nextIndexNum = 0;
 
+    Console.trace("BACKUPFILES backupFiles.length: " + backupFiles.length);
     if (backupFiles.length == 0)
     {
       // No other backup files. Just need to move existing file to backupfile_1
+      Console.trace(
+              "BACKUPFILES no existing backup files, setting index to 1");
       nextIndexNum = 1;
     }
     else
@@ -276,10 +311,7 @@ public class BackupFiles
       if (reverseOrder)
       {
         // backup style numbering
-
-        File lastfile = null;
-        File lastfiletobedeleted = null;
-        String lastfiletobedeletedoriginalname = null;
+        Console.trace("BACKUPFILES rolling files in reverse order");
 
         int tempMax = noMax ? -1 : max;
         // noMax == true means no limits
@@ -296,7 +328,9 @@ public class BackupFiles
             tempMax = i;
           }
         }
-        
+
+        File previousFile = null;
+        File fileToBeDeleted = null;
         for (int n = tempMax; n > 0; n--)
         {
           String backupfilename = dir + File.separatorChar
@@ -307,101 +341,204 @@ public class BackupFiles
           if (!backupfile_n.exists())
           {
             // no "oldest" file to delete
-            lastfile = backupfile_n;
-            lastfiletobedeleted = null;
+            previousFile = backupfile_n;
+            fileToBeDeleted = null;
+            Console.trace("BACKUPFILES No oldest file to delete");
             continue;
           }
 
-          // check the modification time of the previous file if it's going to
-          // be deleted
-          if (lastfiletobedeleted != null)
+          // check the modification time of this (backupfile_n) and the previous
+          // file (fileToBeDeleted) if the previous file is going to be deleted
+          if (fileToBeDeleted != null)
           {
-            long oldLMT = lastfiletobedeleted.lastModified();
-            long newLMT = backupfile_n.lastModified();
-            if (oldLMT > newLMT)
+            File replacementFile = backupfile_n;
+            long fileToBeDeletedLMT = fileToBeDeleted.lastModified();
+            long replacementFileLMT = replacementFile.lastModified();
+            Console.trace("BACKUPFILES fileToBeDeleted is "
+                    + fileToBeDeleted.getAbsolutePath());
+            Console.trace("BACKUPFILES replacementFile is "
+                    + backupfile_n.getAbsolutePath());
+
+            try
+            {
+              File oldestTempFile = nextTempFile(fileToBeDeleted.getName(),
+                      dirFile);
+
+              if (fileToBeDeletedLMT > replacementFileLMT)
+              {
+                String fileToBeDeletedLMTString = sdf
+                        .format(fileToBeDeletedLMT);
+                String replacementFileLMTString = sdf
+                        .format(replacementFileLMT);
+                Console.warn("WARNING! I am set to delete backupfile "
+                        + fileToBeDeleted.getName()
+                        + " has modification time "
+                        + fileToBeDeletedLMTString
+                        + " which is newer than its replacement "
+                        + replacementFile.getName()
+                        + " with modification time "
+                        + replacementFileLMTString);
+
+                boolean delete = confirmNewerDeleteFile(fileToBeDeleted,
+                        replacementFile, true);
+                Console.trace("BACKUPFILES " + (delete ? "confirmed" : "not")
+                        + " deleting file "
+                        + fileToBeDeleted.getAbsolutePath()
+                        + " which is newer than "
+                        + replacementFile.getAbsolutePath());
+
+                if (delete)
+                {
+                  // User has confirmed delete -- no need to add it to the list
+                  fileToBeDeleted.delete();
+                }
+                else
+                {
+                  Console.debug("BACKUPFILES moving "
+                          + fileToBeDeleted.getAbsolutePath() + " to "
+                          + oldestTempFile.getAbsolutePath());
+                  moveFileToFile(fileToBeDeleted, oldestTempFile);
+                }
+              }
+              else
+              {
+                Console.debug("BACKUPFILES going to move "
+                        + fileToBeDeleted.getAbsolutePath() + " to "
+                        + oldestTempFile.getAbsolutePath());
+                moveFileToFile(fileToBeDeleted, oldestTempFile);
+                addDeleteFile(oldestTempFile);
+              }
+
+            } catch (Exception e)
             {
-              String oldLMTString = sdf
-                      .format(lastfiletobedeleted.lastModified());
-              String newLMTString = sdf.format(backupfile_n.lastModified());
-              System.out.println("WARNING! I am set to delete backupfile "
-                      + lastfiletobedeleted.getName() + " (was '"
-                      + lastfiletobedeletedoriginalname + "')"
-                      + " has modification time "
-                      + oldLMTString
-                      + " which is newer than its replacement "
-                      + backupfile_n.getName() + " with modification time "
-                      + newLMTString);
-
-              addDeleteFile(lastfiletobedeleted, backupfile_n, true, true,
-                      " (" + MessageManager.formatMessage(
-                              "label.was_previous", new String[]
-                              { lastfile.getName() }) + ")");
+              Console.error(
+                      "Error occurred, probably making new temp file for '"
+                              + fileToBeDeleted.getName() + "'");
+              Console.error(Cache.getStackTraceString(e));
             }
 
             // reset
-            lastfiletobedeleted = null;
-            lastfiletobedeletedoriginalname = null;
+            fileToBeDeleted = null;
           }
 
           if (!noMax && n == tempMax && backupfile_n.exists())
           {
-            // move the largest (max) rolled file to a temp file and add to the
-            // delete list
-            try
-            {
-              File temp = File.createTempFile(backupfilename, TEMP_FILE_EXT,
-                      dirFile);
-              backupfile_n.renameTo(temp);
-
-              String message = "(" + MessageManager
-                      .formatMessage("label.was_previous", new String[]
-                      { backupfile_n.getName() }) + ")";
-              addDeleteFile(temp, backupfile_n, true, false, message);
-
-              lastfiletobedeleted = temp;
-            } catch (IOException e)
-            {
-              System.out.println(
-                      "IOException when creating temporary file for backupfilename");
-            }
+            fileToBeDeleted = backupfile_n;
           }
           else
           {
-            // Just In Case
-            if (lastfile != null)
+            if (previousFile != null)
             {
-              ret = ret && backupfile_n.renameTo(lastfile);
+              // using boolean '&' instead of '&&' as don't want moveFileToFile
+              // attempt to be conditional (short-circuit)
+              ret = ret & moveFileToFile(backupfile_n, previousFile);
             }
           }
 
-          lastfile = backupfile_n;
+          previousFile = backupfile_n;
         }
 
         // index to use for the latest backup
         nextIndexNum = 1;
       }
-      else
+      else // not reverse numbering
       {
         // version style numbering (with earliest file deletion if max files
         // reached)
 
         bfTreeMap.values().toArray(backupFiles);
+        StringBuilder bfsb = new StringBuilder();
+        for (int i = 0; i < backupFiles.length; i++)
+        {
+          if (bfsb.length() > 0)
+          {
+            bfsb.append(", ");
+          }
+          bfsb.append(backupFiles[i].getName());
+        }
+        Console.trace("BACKUPFILES backupFiles: " + bfsb.toString());
 
         // noMax == true means keep all backup files
         if ((!noMax) && bfTreeMap.size() >= max)
         {
+          Console.trace("BACKUPFILES noMax: " + noMax + ", " + "max: " + max
+                  + ", " + "bfTreeMap.size(): " + bfTreeMap.size());
           // need to delete some files to keep number of backups to designated
-          // max
-          int numToDelete = bfTreeMap.size() - max + 1;
+          // max.
+          // Note that if the suffix is not numbered then do not delete any
+          // backup files later or we'll delete the new backup file (there can
+          // be only one).
+          int numToDelete = suffix.indexOf(NUM_PLACEHOLDER) > -1
+                  ? bfTreeMap.size() - max + 1
+                  : 0;
+          Console.trace("BACKUPFILES numToDelete: " + numToDelete);
+          // the "replacement" file is the latest backup file being kept (it's
+          // not replacing though)
+          File replacementFile = numToDelete < backupFiles.length
+                  ? backupFiles[numToDelete]
+                  : null;
           for (int i = 0; i < numToDelete; i++)
           {
-            addDeleteFile(backupFiles[i], null, true, false, null);
+            // check the deletion files for modification time of the last
+            // backupfile being saved
+            File fileToBeDeleted = backupFiles[i];
+            boolean delete = true;
+
+            Console.trace("BACKUPFILES fileToBeDeleted: " + fileToBeDeleted);
+
+            boolean newer = false;
+            if (replacementFile != null)
+            {
+              long fileToBeDeletedLMT = fileToBeDeleted.lastModified();
+              long replacementFileLMT = replacementFile != null
+                      ? replacementFile.lastModified()
+                      : Long.MAX_VALUE;
+              if (fileToBeDeletedLMT > replacementFileLMT)
+              {
+                String fileToBeDeletedLMTString = sdf
+                        .format(fileToBeDeletedLMT);
+                String replacementFileLMTString = sdf
+                        .format(replacementFileLMT);
+
+                Console.warn("WARNING! I am set to delete backupfile '"
+                        + fileToBeDeleted.getName()
+                        + "' has modification time "
+                        + fileToBeDeletedLMTString
+                        + " which is newer than the oldest backupfile being kept '"
+                        + replacementFile.getName()
+                        + "' with modification time "
+                        + replacementFileLMTString);
+
+                delete = confirmNewerDeleteFile(fileToBeDeleted,
+                        replacementFile, false);
+                if (delete)
+                {
+                  // User has confirmed delete -- no need to add it to the list
+                  fileToBeDeleted.delete();
+                  Console.debug("BACKUPFILES deleting fileToBeDeleted: "
+                          + fileToBeDeleted);
+                  delete = false;
+                }
+                else
+                {
+                  // keeping file, nothing to do!
+                  Console.debug("BACKUPFILES keeping fileToBeDeleted: "
+                          + fileToBeDeleted);
+                }
+              }
+            }
+            if (delete)
+            {
+              addDeleteFile(fileToBeDeleted);
+              Console.debug("BACKUPFILES addDeleteFile(fileToBeDeleted): "
+                      + fileToBeDeleted);
+            }
+
           }
 
         }
 
         nextIndexNum = bfTreeMap.lastKey() + 1;
-
       }
     }
 
@@ -409,108 +546,168 @@ public class BackupFiles
     String latestBackupFilename = dir + File.separatorChar
             + BackupFilenameParts.getBackupFilename(nextIndexNum, basename,
                     suffix, digits);
-    nextBackupFile = new File(latestBackupFilename);
-    ret |= file.renameTo(nextBackupFile);
-
+    Console.trace("BACKUPFILES Moving old file [" + file
+            + "] to latestBackupFilename [" + latestBackupFilename + "]");
+    // using boolean '&' instead of '&&' as don't want moveFileToFile attempt to
+    // be conditional (short-circuit)
+    ret = ret & moveFileToFile(file, new File(latestBackupFilename));
+    Console.debug("BACKUPFILES moving " + file + " to " + latestBackupFilename
+            + " was " + (ret ? "" : "NOT ") + "successful");
     if (tidyUp)
     {
+      Console.debug("BACKUPFILES tidying up files");
       tidyUpFiles();
     }
 
     return ret;
   }
 
+  private static File nextTempFile(String filename, File dirFile)
+          throws IOException
+  {
+    File temp = null;
+    COUNT: for (int i = 1; i < 1000; i++)
+    {
+      File trythis = new File(dirFile,
+              filename + '~' + Integer.toString(i));
+      if (!trythis.exists())
+      {
+        temp = trythis;
+        break COUNT;
+      }
+
+    }
+    if (temp == null)
+    {
+      temp = File.createTempFile(filename, TEMP_FILE_EXT, dirFile);
+    }
+    return temp;
+  }
+
   private void tidyUpFiles()
   {
     deleteOldFiles();
   }
 
-  private void deleteOldFiles()
+  private static boolean confirmNewerDeleteFile(File fileToBeDeleted,
+          File replacementFile, boolean replace)
   {
-    if (deleteFiles != null && !deleteFiles.isEmpty())
-    {
-      boolean confirm = confirmDelete;
-      // delete old backup/version files
+    StringBuilder messageSB = new StringBuilder();
 
-      // check for newer files
-      boolean newerDelete = hasNewerDeleteFile();
-      StringBuilder newerDeleteSB = null;
-      if (newerDelete)
+    File ftbd = fileToBeDeleted;
+    String ftbdLMT = sdf.format(ftbd.lastModified());
+    String ftbdSize = Long.toString(ftbd.length());
+
+    File rf = replacementFile;
+    String rfLMT = sdf.format(rf.lastModified());
+    String rfSize = Long.toString(rf.length());
+
+    int confirmButton = JvOptionPane.NO_OPTION;
+    if (replace)
+    {
+      File saveFile = null;
+      try
       {
-        newerDeleteSB = new StringBuilder();
-        for (int i = 0; i < deleteFiles.size(); i++)
-        {
-          DeleteFile df = deleteFiles.get(i);
-          if (df.newer && df.delete)
-          {
-            String oldName = df.oldFile.getName();
-            String oldLMT = sdf.format(df.oldFile.lastModified());
-            String newLMT = sdf.format(df.newFile.lastModified());
-            if (newerDeleteSB.length() > 0)
-            {
-              newerDeleteSB.append("\n");
-            }
-            newerDeleteSB.append(
-                    MessageManager.formatMessage("label.newerdelete_line",
-                            new String[]
-                            { oldName, oldLMT, df.newFile.getName(),
-                                newLMT })
-            );
-            if (df.info != null
-                  && df.info.length() > 0)
-            {
-              newerDeleteSB.append(" ");
-              newerDeleteSB.append(df.info);
-            }
-            confirm = true;
-          }
-        }
+        saveFile = nextTempFile(ftbd.getName(), ftbd.getParentFile());
+      } catch (Exception e)
+      {
+        Console.error(
+                "Error when confirming to keep backup file newer than other backup files.");
+        e.printStackTrace();
       }
+      messageSB.append(MessageManager.formatMessage(
+              "label.newerdelete_replacement_line", new String[]
+              { ftbd.getName(), rf.getName(), ftbdLMT, rfLMT, ftbdSize,
+                  rfSize }));
+      // "Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted and
+      // replaced by apparently older file \n''{1}''\t(modified {3}, size
+      // {5}).""
+      messageSB.append("\n\n");
+      messageSB.append(MessageManager.formatMessage(
+              "label.confirm_deletion_or_rename", new String[]
+              { ftbd.getName(), saveFile.getName() }));
+      // "Confirm deletion of ''{0}'' or rename to ''{1}''?"
+      String[] options = new String[] {
+          MessageManager.getString("label.delete"),
+          MessageManager.getString("label.rename") };
+
+      confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
+              : JvOptionPane.showOptionDialog(Desktop.desktop,
+                      messageSB.toString(),
+                      MessageManager.getString(
+                              "label.backupfiles_confirm_delete"),
+                      // "Confirm delete"
+                      JvOptionPane.YES_NO_OPTION,
+                      JvOptionPane.WARNING_MESSAGE, null, options,
+                      options[0]);
+    }
+    else
+    {
+      messageSB.append(MessageManager
+              .formatMessage("label.newerdelete_line", new String[]
+              { ftbd.getName(), rf.getName(), ftbdLMT, rfLMT, ftbdSize,
+                  rfSize }));
+      // "Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted but
+      // is newer than the oldest remaining backup file \n''{1}''\t(modified
+      // {3}, size {5})."
+      messageSB.append("\n\n");
+      messageSB.append(MessageManager
+              .formatMessage("label.confirm_deletion", new String[]
+              { ftbd.getName() }));
+      // "Confirm deletion of ''{0}''?"
+      String[] options = new String[] {
+          MessageManager.getString("label.delete"),
+          MessageManager.getString("label.keep") };
+
+      confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
+              : JvOptionPane.showOptionDialog(Desktop.desktop,
+                      messageSB.toString(),
+                      MessageManager.getString(
+                              "label.backupfiles_confirm_delete"),
+                      // "Confirm delete"
+                      JvOptionPane.YES_NO_OPTION,
+                      JvOptionPane.WARNING_MESSAGE, null, options,
+                      options[0]);
+    }
 
+    // return should be TRUE if file is to be deleted
+    return (confirmButton == JvOptionPane.YES_OPTION);
+  }
+
+  private void deleteOldFiles()
+  {
+    if (deleteFiles != null && !deleteFiles.isEmpty())
+    {
       boolean doDelete = false;
-      StringBuilder deleteSB = null;
+      StringBuilder messageSB = null;
       if (confirmDelete && deleteFiles.size() > 0)
       {
-        deleteSB = new StringBuilder();
-        deleteSB.append(MessageManager
+        messageSB = new StringBuilder();
+        messageSB.append(MessageManager
                 .getString("label.backupfiles_confirm_delete_old_files"));
+        // "Delete the following older backup files? (see the Backups tab in
+        // Preferences for more options)"
         for (int i = 0; i < deleteFiles.size(); i++)
         {
-          DeleteFile df = deleteFiles.get(i);
-          if (!df.delete)
-          {
-            break;
-          }
-          deleteSB.append("\n");
-          deleteSB.append(df.oldFile.getName());
-          if (df.info != null
-                && df.info.length() > 0)
-          {
-            deleteSB.append("\n");
-            deleteSB.append(df.info);
-          }
-        }
-        confirm = true;
-      }
-
-      if (confirm)
-      {
-        StringBuilder messageSB = new StringBuilder();
-        if (deleteSB != null && deleteSB.length() > 0)
-        {
-          messageSB.append(deleteSB);
-        }
-        if (newerDeleteSB != null && newerDeleteSB.length() > 0)
-        {
+          File df = deleteFiles.get(i);
           messageSB.append("\n");
-          messageSB.append(newerDeleteSB);
+          messageSB.append(df.getName());
+          messageSB.append(" ");
+          messageSB.append(MessageManager.formatMessage("label.file_info",
+                  new String[]
+                  { sdf.format(df.lastModified()),
+                      Long.toString(df.length()) }));
+          // "(modified {0}, size {1})"
         }
 
-        int confirmButton = JvOptionPane.showConfirmDialog(Desktop.desktop,
-                messageSB.toString(),
-                MessageManager
-                        .getString("label.backupfiles_confirm_delete"),
-                JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE);
+        int confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
+                : JvOptionPane.showConfirmDialog(Desktop.desktop,
+                        messageSB.toString(),
+                        MessageManager.getString(
+                                "label.backupfiles_confirm_delete"),
+                        // "Confirm delete"
+                        JvOptionPane.YES_NO_OPTION,
+                        JvOptionPane.WARNING_MESSAGE);
 
         doDelete = (confirmButton == JvOptionPane.YES_OPTION);
       }
@@ -523,10 +720,11 @@ public class BackupFiles
       {
         for (int i = 0; i < deleteFiles.size(); i++)
         {
-          File fileToDelete = deleteFiles.get(i).oldFile;
+          File fileToDelete = deleteFiles.get(i);
+          Console.trace("BACKUPFILES about to delete fileToDelete:"
+                  + fileToDelete);
           fileToDelete.delete();
-          // System.out.println("DELETING '" + fileToDelete.getName() +
-          // "'");
+          Console.warn("deleted '" + fileToDelete.getName() + "'");
         }
       }
 
@@ -536,8 +734,7 @@ public class BackupFiles
   }
 
   private TreeMap<Integer, File> sortBackupFilesAsTreeMap(
-          File[] backupFiles,
-          String basename)
+          File[] backupFiles, String basename)
   {
     // sort the backup files (based on integer found in the suffix) using a
     // precomputed Hashmap for speed
@@ -562,7 +759,7 @@ public class BackupFiles
     boolean rename = false;
     if (write)
     {
-      roll = this.rollBackupFiles(false);
+      roll = this.rollBackupFiles(false); // tidyUpFiles at the end
       rename = this.renameTempFile();
     }
 
@@ -576,7 +773,9 @@ public class BackupFiles
     if (!okay)
     {
       StringBuilder messageSB = new StringBuilder();
-      messageSB.append(MessageManager.getString( "label.backupfiles_confirm_save_file_backupfiles_roll_wrong"));
+      messageSB.append(MessageManager.getString(
+              "label.backupfiles_confirm_save_file_backupfiles_roll_wrong"));
+      // "Something possibly went wrong with the backups of this file."
       if (rename)
       {
         if (messageSB.length() > 0)
@@ -585,6 +784,7 @@ public class BackupFiles
         }
         messageSB.append(MessageManager.getString(
                 "label.backupfiles_confirm_save_new_saved_file_ok"));
+        // "The new saved file seems okay."
       }
       else
       {
@@ -594,13 +794,22 @@ public class BackupFiles
         }
         messageSB.append(MessageManager.getString(
                 "label.backupfiles_confirm_save_new_saved_file_not_ok"));
+        // "The new saved file might not be okay."
       }
-
-      int confirmButton = JvOptionPane.showConfirmDialog(Desktop.desktop,
-              messageSB.toString(),
-              MessageManager
-                      .getString("label.backupfiles_confirm_save_file"),
-              JvOptionPane.OK_OPTION, JvOptionPane.WARNING_MESSAGE);
+      if (messageSB.length() > 0)
+      {
+        messageSB.append("\n");
+      }
+      messageSB
+              .append(MessageManager.getString("label.continue_operation"));
+
+      int confirmButton = Platform.isHeadless() ? JvOptionPane.OK_OPTION
+              : JvOptionPane.showConfirmDialog(Desktop.desktop,
+                      messageSB.toString(),
+                      MessageManager.getString(
+                              "label.backupfiles_confirm_save_file"),
+                      // "Confirm save file"
+                      JvOptionPane.OK_OPTION, JvOptionPane.WARNING_MESSAGE);
       okay = confirmButton == JvOptionPane.OK_OPTION;
     }
     if (okay)
@@ -624,8 +833,7 @@ public class BackupFiles
       dirFile = file.getParentFile();
     } catch (Exception e)
     {
-      System.out.println(
-              "Could not get canonical path for file '" + file + "'");
+      Console.error("Could not get canonical path for file '" + file + "'");
       return new TreeMap<>();
     }
 
@@ -653,69 +861,62 @@ public class BackupFiles
     return bfTreeMap;
   }
 
-  private boolean addDeleteFile(File oldFile, File newFile, boolean delete,
-          boolean newer, String info)
+  /*
+  private boolean addDeleteFile(File fileToBeDeleted, File originalFile,
+          boolean delete, boolean newer)
+  {
+    return addDeleteFile(fileToBeDeleted, originalFile, null, delete, newer);
+  }
+  */
+  private boolean addDeleteFile(File fileToBeDeleted)
   {
     boolean ret = false;
-    int pos = deleteFiles.indexOf(oldFile);
+    int pos = deleteFiles.indexOf(fileToBeDeleted);
     if (pos > -1)
     {
-      DeleteFile df = deleteFiles.get(pos);
-      if (newFile != null)
-      {
-        df.newFile = newFile;
-      }
-      df.delete |= delete;
-      df.newer |= newer;
-      df.info += ';' + info;
-      ret = true;
+      Console.debug("BACKUPFILES not adding file "
+              + fileToBeDeleted.getAbsolutePath()
+              + " to the delete list (already at index" + pos + ")");
+      return true;
     }
     else
     {
-      deleteFiles
-              .add(new DeleteFile(oldFile, newFile, delete, newer, info));
+      Console.debug("BACKUPFILES adding file "
+              + fileToBeDeleted.getAbsolutePath() + " to the delete list");
+      deleteFiles.add(fileToBeDeleted);
     }
     return ret;
   }
 
-  private boolean hasNewerDeleteFile()
+  public static boolean moveFileToFile(File oldFile, File newFile)
   {
-    for (int i = 0; i < deleteFiles.size(); i++)
+    Console.initLogger();
+    boolean ret = false;
+    Path oldPath = Paths.get(oldFile.getAbsolutePath());
+    Path newPath = Paths.get(newFile.getAbsolutePath());
+    try
     {
-      DeleteFile df = deleteFiles.get(i);
-      if (df.newer)
-      {
-        return true;
-      }
+      // delete destination file - not usually necessary but Just In Case...
+      Console.trace("BACKUPFILES deleting " + newFile.getAbsolutePath());
+      newFile.delete();
+      Console.trace("BACKUPFILES moving " + oldFile.getAbsolutePath() + " to "
+              + newFile.getAbsolutePath());
+      Files.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING);
+      ret = true;
+      Console.trace("BACKUPFILES move seems to have succeeded");
+    } catch (IOException e)
+    {
+      Console.warn("Could not move file '" + oldPath.toString() + "' to '"
+              + newPath.toString() + "'");
+      Console.error(e.getMessage());
+      Console.debug(Cache.getStackTraceString(e));
+      ret = false;
+    } catch (Exception e)
+    {
+      Console.error(e.getMessage());
+      Console.debug(Cache.getStackTraceString(e));
+      ret = false;
     }
-    return false;
-  }
-}
-
-class DeleteFile
-{
-  protected File oldFile;
-
-  protected File newFile;
-
-  protected boolean delete;
-
-  protected boolean newer;
-
-  protected String info;
-
-  protected DeleteFile(File oldFile, File newFile, boolean delete,
-          boolean newer, String info)
-  {
-    this.oldFile = oldFile;
-    this.newFile = newFile;
-    this.delete = delete;
-    this.newer = newer;
-    this.info = info;
-  }
-
-  public boolean equals(File file)
-  {
-    return this.oldFile.equals(file);
+    return ret;
   }
 }