/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.client;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.swing.Timer;
import org.apache.log4j.Logger;
import com.t3.language.I18N;
import com.t3.model.campaign.Campaign;
import com.t3.persistence.PersistenceUtil;
/**
* @author tylere
*
* Attempts to recover campaigns when the application crashes.
*/
public class AutoSaveManager implements ActionListener {
private static final Logger log = Logger.getLogger(AutoSaveManager.class);
private Timer autoSaveTimer;
public static final File AUTOSAVE_FILE = new File(AppUtil.getAppHome("autosave"), "AutoSave" + AppConstants.CAMPAIGN_FILE_EXTENSION); //$NON-NLS-1$
public void start() {
restart();
}
/**
* Queries the auto-save increment from {@link AppPreferences} and starts a new timer.
*/
public void restart() {
int interval = AppPreferences.getAutoSaveIncrement();
//convert to milliseconds
int delay = interval * 60 * 1000;
if (log.isDebugEnabled())
log.debug("Starting autosave manager; interval in seconds is " + (interval * 60)); //$NON-NLS-1$
if (autoSaveTimer == null) {
if (interval <= 0) { // auto-save is turned off with <= 0
return;
} else {
autoSaveTimer = new Timer(delay, this);
autoSaveTimer.start(); // Start it running...
}
} else {
if (interval <= 0) {
autoSaveTimer.stop(); // auto-save is off; stop the Timer first
autoSaveTimer = null;
} else {
autoSaveTimer.setDelay(delay);
autoSaveTimer.restart(); // Set the new delay and restart the Timer
}
}
}
/**
* Applications can use this to pause the timer. The {@link #restart()} method can be called at any time to reset
* and start the timer.
*/
public void pause() {
if (autoSaveTimer != null && autoSaveTimer.isRunning())
autoSaveTimer.stop();
}
@Override
public void actionPerformed(ActionEvent e) {
// Don't autosave if we don't "own" the campaign
if (!TabletopTool.isHostingServer() && !TabletopTool.isPersonalServer()) {
return;
}
if (AppState.isSaving()) {
log.debug("Canceling autosave because user has initiated save operation"); //$NON-NLS-1$
return;
}
try {
TabletopTool.getFrame().setStatusMessage(I18N.getString("AutoSaveManager.status.autoSaving"));
long startCopy = System.currentTimeMillis();
// This occurs on the event dispatch thread, so it's ok to mess with the models.
// We need to clone the campaign so that we can save in the background, but
// not have concurrency issues with the original model.
//
// NOTE: This is a cheesy way to clone the campaign, but it makes it so that I
// don't have to keep all the various models' clone methods updated on each change.
final Campaign campaign = new Campaign(TabletopTool.getCampaign());
if (log.isInfoEnabled())
log.info("Time to copy Campaign object (ms): " + (System.currentTimeMillis() - startCopy)); //$NON-NLS-1$
// Now that we have a copy of the model, save that one
// TODO: Replace this with a swing worker
new Thread(null, new Runnable() {
@Override
public void run() {
AppState.setIsSaving(true);
pause();
long startSave = System.currentTimeMillis();
try {
PersistenceUtil.saveCampaign(campaign, AUTOSAVE_FILE);
TabletopTool.getFrame().setStatusMessage(I18N.getText("AutoSaveManager.status.autoSaveComplete", System.currentTimeMillis() - startSave));
} catch (IOException ioe) {
TabletopTool.showError("AutoSaveManager.failed", ioe);
} catch (Throwable t) {
TabletopTool.showError("AutoSaveManager.failed", t);
} finally {
AppState.setIsSaving(false);
}
}
}, "AutoSaveThread").start();
} catch (Throwable t) {
// If this routine fails, be sure the isSaving is turned off. This should not be necessary:
// If the exception occurs anywhere before the .start() method of Thread, the boolean
// does not need to be reset anyway. And if the .start() method is successful, this code
// will never be invoked, in which case the .run() method will decide when to set/reset
// the flag. For safety's sake I retrieve the current value and report it if it's true, but
// we shouldn't be able to get here in that case...
if (AppState.isSaving()) {
TabletopTool.showError(I18N.getString("AutoSaveManager.failed") + "<br/>\nand AppState.isSaving() is true!", t);
AppState.setIsSaving(false);
} else {
TabletopTool.showError("AutoSaveManager.failed", t);
}
}
}
/**
* Removes any autosaved files
*/
public void purge() {
if (AUTOSAVE_FILE.exists()) {
AUTOSAVE_FILE.delete();
}
}
/**
* Removes the campaignFile if it's from Autosave, forcing to save as new
*/
public void tidy() {
if (AUTOSAVE_FILE.equals(AppState.getCampaignFile())) {
AppState.setCampaignFile(null);
}
purge();
}
/**
* Check to see if autosave recovery is necessary.
*/
public void check() {
if (AUTOSAVE_FILE.exists()) {
boolean okay;
okay = TabletopTool.confirm("msg.confirm.recoverAutosave", AUTOSAVE_FILE.lastModified());
if (okay)
AppActions.loadCampaign(AUTOSAVE_FILE);
}
}
}