Auto-save plugin now works by periodically creating project backups in

user\.diylc\autoSave directory and maintains file history, keeping the
last X versions of the file. Maximum size for all backups is
configurable and pre-set to 64MB
This commit is contained in:
Branislav Stojkovic
2019-03-22 16:38:01 +01:00
parent 14a994e68f
commit 9366f9c602
4 changed files with 82 additions and 67 deletions
Binary file not shown.
Binary file not shown.
@@ -21,86 +21,48 @@
*/ */
package org.diylc.swing.plugins.autosave; package org.diylc.swing.plugins.autosave;
import java.io.File; import java.io.File;
import java.util.Date; import java.text.DateFormat;
import java.util.EnumSet; import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService; import java.util.Arrays;
import java.util.concurrent.Executors; import java.util.Comparator;
import java.util.Date;
import javax.swing.SwingUtilities; import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import org.diylc.appframework.miscutils.ConfigurationManager; import java.util.concurrent.Executors;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.common.EventType; import org.apache.log4j.Logger;
import org.diylc.common.IPlugIn; import org.diylc.appframework.miscutils.Utils;
import org.diylc.common.IPlugInPort; import org.diylc.common.EventType;
import org.diylc.core.IView; import org.diylc.common.IPlugIn;
import org.diylc.presenter.Presenter; import org.diylc.common.IPlugInPort;
import org.diylc.swing.gui.DummyView; import org.diylc.core.IView;
public class AutoSavePlugin implements IPlugIn { public class AutoSavePlugin implements IPlugIn {
private static final String AUTO_SAVE_FILE_NAME = Utils.getUserDataDirectory("diylc") + "autoSave.diy"; private static final String AUTO_SAVE_PATH = Utils.getUserDataDirectory("diylc") + "autoSave";
private static final Logger LOG = Logger.getLogger(AutoSavePlugin.class);
protected static final long autoSaveFrequency = 60 * 1000; protected static final long autoSaveFrequencyMs = 60 * 1000;
protected static final int maxTotalSizeMb = 64;
private ExecutorService executor; private ExecutorService executor;
private IPlugInPort plugInPort; private IPlugInPort plugInPort;
private IView view;
private long lastSave = 0; private long lastSave = 0;
public AutoSavePlugin(IView view) { public AutoSavePlugin(IView view) {
this.view = view;
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor();
} }
@Override @Override
public void connect(IPlugInPort plugInPort) { public void connect(IPlugInPort plugInPort) {
this.plugInPort = plugInPort; this.plugInPort = plugInPort;
SwingUtilities.invokeLater(new Runnable() { // create the directory if needed
File dir = new File(AUTO_SAVE_PATH);
@Override if (!dir.exists())
public void run() { dir.mkdirs();
boolean wasAbnormal = ConfigurationManager.getInstance().readBoolean(IPlugInPort.ABNORMAL_EXIT_KEY, false);
Date lastHeartbeat = (Date) ConfigurationManager.getInstance().readObject(IPlugInPort.HEARTBEAT, new Date());
long msSinceHeartbeat = new Date().getTime() - lastHeartbeat.getTime();
File autoSaved = new File(AUTO_SAVE_FILE_NAME);
if (autoSaved.exists())
// try to figure out if another instance is running. Only pull auto-saved file if there's no recent heartbeat
if (wasAbnormal && msSinceHeartbeat > autoSaveFrequency) {
IPlugInPort testPresenter = new Presenter(new DummyView());
testPresenter.loadProjectFromFile(AUTO_SAVE_FILE_NAME);
// Only prompt if there is something saved in the
// auto-saved file.
if (!testPresenter.getCurrentProject().getComponents().isEmpty()) {
int decision =
view.showConfirmDialog(
"It appears that application was not closed normally in the previous session. Do you want to open the last auto-saved file?",
"Auto-Save", IView.YES_NO_OPTION, IView.QUESTION_MESSAGE);
if (decision == IView.YES_OPTION) {
AutoSavePlugin.this.plugInPort.loadProjectFromFile(AUTO_SAVE_FILE_NAME);
}
}
} else
autoSaved.delete();
// Set abnormal flag to true, GUI side of the app must flip to
// false when app closes regularly.
ConfigurationManager.getInstance().writeValue(IPlugInPort.ABNORMAL_EXIT_KEY, true);
}
});
// write heartbeat periodically
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(autoSaveFrequency);
} catch (InterruptedException e) {
}
ConfigurationManager.getInstance().writeValue(IPlugInPort.HEARTBEAT, new Date());
}
}).start();
} }
@Override @Override
@@ -111,18 +73,67 @@ public class AutoSavePlugin implements IPlugIn {
@Override @Override
public void processMessage(EventType eventType, Object... params) { public void processMessage(EventType eventType, Object... params) {
if (eventType == EventType.PROJECT_MODIFIED) { if (eventType == EventType.PROJECT_MODIFIED) {
if (System.currentTimeMillis() - lastSave > autoSaveFrequency) { if (System.currentTimeMillis() - lastSave > autoSaveFrequencyMs) {
executor.execute(new Runnable() { executor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY); Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
lastSave = System.currentTimeMillis(); lastSave = System.currentTimeMillis();
plugInPort.saveProjectToFile(AUTO_SAVE_FILE_NAME, true); String fileName = generateBackupFileName(plugInPort.getCurrentFileName());
LOG.info("Creating backup file: " + fileName);
plugInPort.saveProjectToFile(fileName, true);
cleanupExtra();
} }
}); });
} }
} }
}
private String generateBackupFileName(String baseFileName) {
if (baseFileName == null)
baseFileName = "Untitled";
File file = new File(baseFileName);
String name = file.getName();
// remove extension
if (name.toLowerCase().endsWith(".diy"))
name = name.substring(name.length() - 4);
// append date and time
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
file = new File(AUTO_SAVE_PATH + File.separator + name + "." + dateFormat.format(date) + ".diy");
// make sure that it doesn't already exist
int i = 2;
while (file.exists()) {
file = new File(AUTO_SAVE_PATH + File.separator + name + "." + dateFormat.format(date) + "-" + i + ".diy");
i++;
}
return file.getAbsolutePath();
}
private void cleanupExtra() {
File[] files = new File(AUTO_SAVE_PATH).listFiles();
// sort files by date
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return new Long(o1.lastModified()).compareTo(o2.lastModified());
}
});
long totalSize = 0;
long maxTotalSize = maxTotalSizeMb * 1024 * 1024;
for (File f : files)
totalSize += f.length();
int i = 0;
while (i < files.length && totalSize > maxTotalSize) {
totalSize -= files[i].length();
LOG.info("Deleteting old backup file: " + files[i].getName());
files[i].delete();
i++;
}
} }
} }
+4
View File
@@ -2633,6 +2633,10 @@
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType> <changeType>NEW_FEATURE</changeType>
<description>Added an optional third control point to leaded components and some schematic symbols that can be used to set component label position. This feature is off by default to keep things backwards compatible and is driven by "Movable Label" property</description> <description>Added an optional third control point to leaded components and some schematic symbols that can be used to set component label position. This feature is off by default to keep things backwards compatible and is driven by "Movable Label" property</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Auto-save plugin now works by periodically creating project backups in user\.diylc\autoSave directory and maintains file history, keeping the last X versions of the file. Maximum size for all backups is configurable and pre-set to 64MB</description>
</org.diylc.appframework.update.Change> </org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType> <changeType>IMPROVEMENT</changeType>