JAL-3141 Backup files being created with default suffix up to 10 backups. Configurabl...
[jalview.git] / src / jalview / io / JalviewFileChooser.java
index 7ccdaa9..983800b 100755 (executable)
@@ -21,6 +21,7 @@
 //////////////////////////////////////////////////////////////////
 package jalview.io;
 
+import jalview.bin.Cache;
 import jalview.gui.JvOptionPane;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
@@ -32,6 +33,7 @@ import java.awt.HeadlessException;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
@@ -64,16 +66,17 @@ public class JalviewFileChooser extends JFileChooser
    * @param selected
    * @return
    */
-  public static JalviewFileChooser forRead(String directory, String selected)
+  public static JalviewFileChooser forRead(String directory,
+          String selected)
   {
-    List<String> extensions = new ArrayList<String>();
-    List<String> descs = new ArrayList<String>();
-    for (FileFormatI format : FileFormat.values())
+    List<String> extensions = new ArrayList<>();
+    List<String> descs = new ArrayList<>();
+    for (FileFormatI format : FileFormats.getInstance().getFormats())
     {
       if (format.isReadable())
       {
         extensions.add(format.getExtensions());
-        descs.add(format.toString());
+        descs.add(format.getName());
       }
     }
     return new JalviewFileChooser(directory,
@@ -94,14 +97,14 @@ public class JalviewFileChooser extends JFileChooser
   {
     // TODO in Java 8, forRead and forWrite can be a single method
     // with a lambda expression parameter for isReadable/isWritable
-    List<String> extensions = new ArrayList<String>();
-    List<String> descs = new ArrayList<String>();
-    for (FileFormatI format : FileFormat.values())
+    List<String> extensions = new ArrayList<>();
+    List<String> descs = new ArrayList<>();
+    for (FileFormatI format : FileFormats.getInstance().getFormats())
     {
       if (format.isWritable())
       {
         extensions.add(format.getExtensions());
-        descs.add(format.toString());
+        descs.add(format.getName());
       }
     }
     return new JalviewFileChooser(directory,
@@ -124,16 +127,14 @@ public class JalviewFileChooser extends JFileChooser
   /**
    * Constructor for a single choice of file extension and description
    * 
-   * @param dir
    * @param extension
    * @param desc
    */
-  public JalviewFileChooser(String dir, String extension, String desc)
+  public JalviewFileChooser(String extension, String desc)
   {
-    // TODO inline dir as Cache.getProperty("LAST_DIRECTORY") ? if applet
-    // builds ok
-    this(dir, new String[] { extension }, new String[] { desc }, desc,
-            true);
+    this(Cache.getProperty("LAST_DIRECTORY"), new String[] { extension },
+            new String[]
+            { desc }, desc, true);
   }
 
   JalviewFileChooser(String dir, String[] extensions, String[] descs,
@@ -142,7 +143,7 @@ public class JalviewFileChooser extends JFileChooser
     super(safePath(dir));
     if (extensions.length == descs.length)
     {
-      List<String[]> formats = new ArrayList<String[]>();
+      List<String[]> formats = new ArrayList<>();
       for (int i = 0; i < extensions.length; i++)
       {
         formats.add(new String[] { extensions[i], descs[i] });
@@ -269,7 +270,7 @@ public class JalviewFileChooser extends JFileChooser
       format = format.substring(0, parenPos).trim();
       try
       {
-        return FileFormat.valueOf(format);
+        return FileFormats.getInstance().forName(format);
       } catch (IllegalArgumentException e)
       {
         System.err.println("Unexpected format: " + format);
@@ -278,6 +279,19 @@ public class JalviewFileChooser extends JFileChooser
     return null;
   }
 
+  File ourselectedFile = null;
+
+  @Override
+  public File getSelectedFile()
+  {
+    File selfile = super.getSelectedFile();
+    if (selfile == null && ourselectedFile != null)
+    {
+      return ourselectedFile;
+    }
+    return selfile;
+  }
+
   @Override
   public int showSaveDialog(Component parent) throws HeadlessException
   {
@@ -285,23 +299,48 @@ public class JalviewFileChooser extends JFileChooser
 
     setDialogType(SAVE_DIALOG);
 
+    this.setSelectedFile(null);
     int ret = showDialog(parent, MessageManager.getString("action.save"));
+    ourselectedFile = getSelectedFile();
 
+    if (getSelectedFile() == null)
+    {
+      // Workaround for Java 9,10 on OSX - no selected file, but there is a
+      // filename typed in
+      try
+      {
+        String filename = ((BasicFileChooserUI) getUI()).getFileName();
+        if (filename != null && filename.length() > 0)
+        {
+          ourselectedFile = new File(getCurrentDirectory(), filename);
+        }
+      } catch (Throwable x)
+      {
+        System.err.println(
+                "Unexpected exception when trying to get filename.");
+        x.printStackTrace();
+      }
+    }
+    if (ourselectedFile == null)
+    {
+      return JalviewFileChooser.CANCEL_OPTION;
+    }
     if (getFileFilter() instanceof JalviewFileFilter)
     {
       JalviewFileFilter jvf = (JalviewFileFilter) getFileFilter();
 
-      if (!jvf.accept(getSelectedFile()))
+      if (!jvf.accept(ourselectedFile))
       {
-        String withExtension = getSelectedFile() + "."
+        String withExtension = getSelectedFile().getName() + "."
                 + jvf.getAcceptableExtension();
-        setSelectedFile(new File(withExtension));
+        ourselectedFile = (new File(getCurrentDirectory(), withExtension));
+        setSelectedFile(ourselectedFile);
       }
     }
     // TODO: ENSURE THAT FILES SAVED WITH A ':' IN THE NAME ARE REFUSED AND THE
     // USER PROMPTED FOR A NEW FILENAME
     if ((ret == JalviewFileChooser.APPROVE_OPTION)
-            && getSelectedFile().exists())
+            && ourselectedFile.exists())
     {
       int confirm = JvOptionPane.showConfirmDialog(parent,
               MessageManager.getString("label.overwrite_existing_file"),
@@ -312,6 +351,97 @@ public class JalviewFileChooser extends JFileChooser
       {
         ret = JalviewFileChooser.CANCEL_OPTION;
       }
+
+      rollBackupFiles(ourselectedFile);
+
+    }
+
+    return ret;
+  }
+
+  // attempts to roll backup files for this file (before overwriting). Returns
+  // true if it rolled all the files, false otherwise.
+  private static boolean rollBackupFiles(File file)
+  {
+
+    if (!file.exists())
+    {
+      // nothing to do
+      return true;
+    }
+
+    // split filename up to insert suffix template in the right place. template
+    // and backupMax can be set in .jalview_properties
+    String backupSuffixTemplate = jalview.bin.Cache
+            .getDefault("BACKUP_SUFFIX", "-bak-%n");
+    int backupMax = jalview.bin.Cache.getDefault("BACKUP_MAX", 10);
+    String dir = "";
+    try
+    {
+      File dirFile = file.getParentFile();
+      dir = dirFile.getCanonicalPath();
+    } catch (Exception e)
+    {
+      System.out.println(
+              "Could not get canonical path for file '" + file + "'");
+      return false;
+    }
+    String filename = file.getName();
+    String basename = filename;
+    String extension = "";
+    int dotchar = filename.lastIndexOf('.');
+    // don't split of filenames with the last '.' at the very beginning or
+    // very end of the filename
+    if ((dotchar > 0) && (dotchar < filename.length() - 1))
+    {
+      basename = filename.substring(0, dotchar);
+      extension = filename.substring(dotchar); // NOTE this includes the '.'
+    }
+
+    boolean ret = true;
+    if (backupMax >= 1)
+    {
+      // Create/move backups up one
+      String numString = null;
+      File lastfile = null;
+      for (int n = backupMax; n > 0; n--)
+      {
+        numString = String.format("%02d", n);
+        String backupSuffix = backupSuffixTemplate.replaceAll("%n",
+                numString);
+        String backupfilename = dir + File.separatorChar + basename
+                + backupSuffix + extension;
+        File backupfile_n = new File(backupfilename);
+
+        if (! backupfile_n.exists()) {
+          lastfile = backupfile_n;
+          continue;
+        }
+        
+        if (n == backupMax-1)
+        { // Move the max backup to /tmp instead of deleting (Just In
+          // Case)
+          String tmpfile = "tmp-" + backupfilename;
+          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
+        {
+          ret = ret && backupfile_n.renameTo(lastfile);
+        }
+
+        lastfile = backupfile_n;
+      }
+      
+      // now actually backup the important file!
+      ret = ret && file.renameTo(lastfile);
     }
 
     return ret;
@@ -373,8 +503,8 @@ public class JalviewFileChooser extends JFileChooser
         }
       });
 
-      this.setBorder(new javax.swing.border.TitledBorder(MessageManager
-              .getString("label.recently_opened")));
+      this.setBorder(new javax.swing.border.TitledBorder(
+              MessageManager.getString("label.recently_opened")));
 
       final JScrollPane scroller = new JScrollPane(list);
 
@@ -400,8 +530,8 @@ public class JalviewFileChooser extends JFileChooser
         @Override
         public void run()
         {
-          scroller.getHorizontalScrollBar().setValue(
-                  scroller.getHorizontalScrollBar().getMaximum());
+          scroller.getHorizontalScrollBar()
+                  .setValue(scroller.getHorizontalScrollBar().getMaximum());
         }
       });