/*FreeMindget - A Program for creating and viewing Mindmaps
*Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin and others.
*See COPYING for Details
*
*This program 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 2
*of the License, or (at your option) any later version.
*
*This program 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 this program; if not, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package freemind.modes.mindmapmode;
import java.awt.Color;
import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.swing.JOptionPane;
import freemind.common.OptionalDontShowMeAgainDialog;
import freemind.common.UnicodeReader;
import freemind.main.FreeMind;
import freemind.main.FreeMindMain;
import freemind.main.HtmlTools;
import freemind.main.Resources;
import freemind.main.Tools;
import freemind.main.XMLParseException;
import freemind.modes.MapAdapter;
import freemind.modes.MindMapLinkRegistry;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController;
import freemind.modes.NodeAdapter;
public class MindMapMapModel extends MapAdapter {
public static final String MAP_INITIAL_START = "<map version=\"";
public static final String RESTORE_MODE_MIND_MAP = "MindMap:";
public static final String FREEMIND_VERSION_UPDATER_XSLT = "freemind/modes/mindmapmode/freemind_version_updater.xslt";
LockManager lockManager;
private MindMapLinkRegistry linkRegistry;
private Timer timerForAutomaticSaving;
/**
* The current version and all other version that don't need XML update for
* sure.
*/
public static final String EXPECTED_START_STRINGS[] = {
MAP_INITIAL_START + FreeMind.XML_VERSION + "\"",
MAP_INITIAL_START + "0.7.1\"" };
//
// Constructors
//
public MindMapMapModel(FreeMindMain frame, ModeController modeController) {
this(null, frame, modeController);
}
public MindMapMapModel(MindMapNodeModel root, FreeMindMain frame,
ModeController modeController) {
super(frame, modeController);
lockManager = Resources.getInstance().getBoolProperty(
"experimental_file_locking_on") ? new LockManager()
: new DummyLockManager();
// register new LinkRegistryAdapter
linkRegistry = new MindMapLinkRegistry();
if (root == null)
root = new MindMapNodeModel(frame.getResourceString("new_mindmap"),
frame, this);
setRoot(root);
readOnly = false;
// automatic save: start timer after the map is completely loaded
EventQueue.invokeLater(new Runnable() {
public void run() {
scheduleTimerForAutomaticSaving();
}
});
}
//
public MindMapLinkRegistry getLinkRegistry() {
return linkRegistry;
}
public String getRestorable() {
return getFile() == null ? null : RESTORE_MODE_MIND_MAP
+ getFile().getAbsolutePath();
}
public void changeNode(MindMapNode node, String newText) {
if (node.toString().startsWith("<html>")) {
node.setUserObject(HtmlTools.unescapeHTMLUnicodeEntity(newText));
} else {
node.setUserObject(newText);
}
nodeChanged(node);
}
//
// Other methods
//
public String toString() {
return getFile() == null ? null : getFile().getName();
}
//
// Export and saving
//
public String getAsHTML(List mindMapNodes) {
// Returns success of the operation.
try {
StringWriter stringWriter = new StringWriter();
BufferedWriter fileout = new BufferedWriter(stringWriter);
MindMapController.saveHTML(mindMapNodes, fileout);
fileout.close();
return stringWriter.toString();
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
return null;
}
}
public String getAsPlainText(List mindMapNodes) {
// Returns success of the operation.
try {
StringWriter stringWriter = new StringWriter();
BufferedWriter fileout = new BufferedWriter(stringWriter);
for (ListIterator it = mindMapNodes.listIterator(); it.hasNext();) {
((MindMapNodeModel) it.next()).saveTXT(fileout,/* depth= */0);
}
fileout.close();
return stringWriter.toString();
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
return null;
}
}
public boolean saveTXT(MindMapNodeModel rootNodeOfBranch, File file) {
// Returns success of the operation.
try {
BufferedWriter fileout = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file), FreeMind.DEFAULT_CHARSET));
rootNodeOfBranch.saveTXT(fileout,/* depth= */0);
fileout.close();
return true;
} catch (Exception e) {
System.err.println("Error in MindMapMapModel.saveTXT(): ");
freemind.main.Resources.getInstance().logException(e);
return false;
}
}
public String getAsRTF(List mindMapNodes) {
// Returns success of the operation.
try {
StringWriter stringWriter = new StringWriter();
BufferedWriter fileout = new BufferedWriter(stringWriter);
saveRTF(mindMapNodes, fileout);
fileout.close();
return stringWriter.toString();
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
return null;
}
}
public boolean saveRTF(List mindMapNodes, BufferedWriter fileout) {
// Returns success of the operation.
try {
// First collect all used colors
HashSet colors = new HashSet();
for (ListIterator it = mindMapNodes.listIterator(); it.hasNext();) {
((MindMapNodeModel) it.next()).collectColors(colors);
}
// Prepare table of colors containing indices to color table
String colorTableString = "{\\colortbl;\\red0\\green0\\blue255;";
// 0 - Automatic, 1 - blue for links
HashMap colorTable = new HashMap();
int colorPosition = 2;
for (Iterator it = colors.iterator(); it.hasNext(); ++colorPosition) {
Color color = (Color) it.next();
colorTableString += "\\red" + color.getRed() + "\\green"
+ color.getGreen() + "\\blue" + color.getBlue() + ";";
colorTable.put(color, new Integer(colorPosition));
}
colorTableString += "}";
fileout.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}"
+ colorTableString
+ "}"
+ "\\viewkind4\\uc1\\pard\\f0\\fs20{}");
// ^ If \\ud is appended here, Unicode does not work in MS Word.
for (ListIterator it = mindMapNodes.listIterator(); it.hasNext();) {
((MindMapNodeModel) it.next()).saveRTF(fileout,/* depth= */0,
colorTable);
}
fileout.write("}");
return true;
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
return false;
}
}
/**
* Return the success of saving
*/
public boolean save(File file) {
boolean result;
synchronized (this) {
result = saveInternal(file, false);
// TODO: Set only, when ok?
if (result) {
setFileTime();
}
}
return result;
}
/**
* This method is intended to provide both normal save routines and saving
* of temporary (internal) files.
*/
private boolean saveInternal(File file, boolean isInternal) {
if (!isInternal && readOnly) { // unexpected situation, yet it's better
// to back it up
System.err.println("Attempt to save read-only map.");
return false;
}
try {
// Generating output Stream
if (timerForAutomaticSaving != null) {
timerForAutomaticSaving.cancel();
}
BufferedWriter fileout = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file), FreeMind.DEFAULT_CHARSET));
// OSSXP.COM: save tree into .mm file, without some attrs(such as node fold status).
getXml(fileout, true, 0);
if(Resources.getInstance().getBoolProperty("wh_save_extra_attrs_in_aux_file"))
{
// OSSXP.COM: save variable attrs(such as node fold status) into .mmx file...
String ext = Tools.getExtension(file.getName());
String mmxFileName = "";
if(!ext.equals("mm"))
{
mmxFileName = "." + file.getName()+".mmx";
}
else
{
mmxFileName = "." + Tools.removeExtension(file.getName()) + ".mmx";
}
File mmxfile = new File(file.getParent(), mmxFileName);
BufferedWriter mmxfileout = new BufferedWriter( new OutputStreamWriter( new FileOutputStream(mmxfile), FreeMind.DEFAULT_CHARSET ) );
getXml(mmxfileout, true, 1);
}
if (!isInternal) {
setFile(file);
setSaved(true);
}
scheduleTimerForAutomaticSaving();
return true;
} catch (FileNotFoundException e) {
String message = Tools.expandPlaceholders(getText("save_failed"),
file.getName());
if (!isInternal)
getFrame().getController().errorMessage(message);
else
getFrame().out(message);
} catch (Exception e) {
logger.severe("Error in MindMapMapModel.save(): ");
freemind.main.Resources.getInstance().logException(e);
}
scheduleTimerForAutomaticSaving();
return false;
}
/**
* writes the content of the map to a writer.
*
* @throws IOException
* @param managed_attr =0|1|2
* 0 (default): save to .mm file. (do not save certain attributes, such as node's fold status)
* 1 : save to .mmx file. (only save auxiliary attributes, such as node's fold status)
* 2 : all-in-one .mm file. the default behavior of vanilla freemind.
*/
public void getXml(Writer fileout, boolean saveInvisible, int managed_attr)
throws IOException {
// OSSXP.COM: write xml declare.
fileout.write("<?xml version=\"1.0\" encoding=\"" + FreeMind.DEFAULT_CHARSET + "\"?>\n");
getXml(fileout, saveInvisible, getRootNode(), managed_attr);
}
/**
* writes the content of the map to a writer.
*
* @throws IOException
*/
public void getXml(Writer fileout, boolean saveInvisible,
MindMapNode pRootNode, int managed_attr) throws IOException {
fileout.write("<map ");
fileout.write("version=\"" + FreeMind.XML_VERSION + "\"");
fileout.write(">\n");
// OSSXP.COM: add notice for this hacked version.
fileout.write("<!-- This file is saved using a hacked version of FreeMind. visit: http://freemind-mmx.sourceforge.net -->\n");
fileout.write("<!-- Orignal FreeMind, can download from http://freemind.sourceforge.net -->\n");
switch (managed_attr)
{
case 0:
fileout.write("<!-- This .mm file is CVS/SVN friendly, some atts are saved in .mmx file. (from ossxp.com) -->\n");
break;
case 1:
fileout.write("<!-- This .mmx files store some extra mm file attributes, which should not check in to CVS/SVN ! -->\n");
break;
case 2:
default:
break;
}
if(managed_attr != 1) {
getRegistry().save(fileout);
}
// OSSXP.COM: managed_attr control whether or not save nodes' fold status into .mm file.
pRootNode.save(fileout, this.getLinkRegistry(), saveInvisible, true, managed_attr);
fileout.write("</map>\n");
fileout.close();
}
public void getXml(Writer fileout) throws IOException {
getXml(fileout, true, 0);
}
public void getFilteredXml(Writer fileout) throws IOException {
getXml(fileout, false, 0);
}
/**
* Attempts to lock the map using a semaphore file
*
* @return If the map is locked, return the name of the locking user,
* otherwise return null.
* @throws Exception
* , when the locking failed for other reasons than that the
* file is being edited.
*/
public String tryToLock(File file) throws Exception {
String lockingUser = lockManager.tryToLock(file);
String lockingUserOfOldLock = lockManager.popLockingUserOfOldLock();
if (lockingUserOfOldLock != null) {
getFrame().getController().informationMessage(
Tools.expandPlaceholders(
getText("locking_old_lock_removed"),
file.getName(), lockingUserOfOldLock));
}
if (lockingUser == null) {
readOnly = false;
} // The map sure is not read only when the locking suceeded.
return lockingUser;
}
public void load(URL url) throws FileNotFoundException, IOException,
XMLParseException, URISyntaxException {
logger.info("Loading file: " + url.toString());
File file = Tools.urlToFile(url);
load(file);
}
public void load(File file) throws FileNotFoundException, IOException {
if (!file.exists()) {
throw new FileNotFoundException(Tools.expandPlaceholders(
getText("file_not_found"), file.getPath()));
}
if (!file.canWrite()) {
readOnly = true;
} else {
// try to lock the map
try {
String lockingUser = tryToLock(file);
if (lockingUser != null) {
getFrame().getController().informationMessage(
Tools.expandPlaceholders(
getText("map_locked_by_open"),
file.getName(), lockingUser));
readOnly = true;
} else {
readOnly = false;
}
} catch (Exception e) { // Thrown by tryToLock
freemind.main.Resources.getInstance().logException(e);
getFrame().getController().informationMessage(
Tools.expandPlaceholders(
getText("locking_failed_by_open"),
file.getName()));
readOnly = true;
}
}
synchronized (this) {
MindMapNodeModel root = loadTree(file);
if (root != null) {
setRoot(root);
}
setFile(file);
setFileTime();
}
}
/** When a map is closed, this method is called. */
public void destroy() {
super.destroy();
lockManager.releaseLock();
lockManager.releaseTimer();
if (timerForAutomaticSaving != null) {
/* cancel the timer, if map is closed. */
timerForAutomaticSaving.cancel();
}
}
MindMapNodeModel loadTree(final File pFile) throws XMLParseException,
IOException {
return loadTree(new FileReaderCreator(pFile));
}
public static class StringReaderCreator implements ReaderCreator {
private final String mString;
public StringReaderCreator(String pString) {
mString = pString;
}
public Reader createReader() throws FileNotFoundException {
return new StringReader(mString);
}
public String toString() {
return mString;
}
public File getFile() {
return null;
}
}
private static class FileReaderCreator implements ReaderCreator {
private final File mFile;
public FileReaderCreator(File pFile) {
mFile = pFile;
}
public Reader createReader() throws FileNotFoundException {
return new UnicodeReader(new FileInputStream(mFile), "UTF-8");
}
public String toString() {
return mFile.getName();
}
public File getFile() {
return mFile;
}
}
public interface ReaderCreator {
Reader createReader() throws FileNotFoundException;
File getFile();
}
MindMapNodeModel loadTree(ReaderCreator pReaderCreator)
throws XMLParseException, IOException {
return loadTree(pReaderCreator, true);
}
public MindMapNodeModel loadTree(ReaderCreator pReaderCreator,
boolean pAskUserBeforeUpdate) throws XMLParseException, IOException {
int versionInfoLength;
versionInfoLength = 0;
// reading the start of the file:
// OSSXP.COM:
// because we add a xml declare, the mm file now begin with "<xml",
// so direct match mmap version will failed.
// search "<map" and store matched line into buffer.
BufferedReader in=null;
String buffer = null;
try {
// get the file start into the memory:
in = new BufferedReader(pReaderCreator.createReader());
while ((buffer = in.readLine()) != null) {
// buffer contains line start with "<map", stop matching.
if (buffer.substring(0,4).equals("<map"))
{
break;
}
}
in.close();
} catch (Exception e) {
e.printStackTrace();
buffer = "";
}
// the resulting file is accessed by the reader:
Reader reader = null;
// OSSXP.COM: We need filename when do Tool.getActualReader,
// for we need join .mm file with .mmx file.
File file = pReaderCreator.getFile();
for (int i = 0; i < EXPECTED_START_STRINGS.length; i++) {
versionInfoLength = EXPECTED_START_STRINGS[i].length();
String mapStart = "";
if (buffer.length() >= versionInfoLength) {
mapStart = buffer.substring(0, versionInfoLength);
}
if (mapStart.startsWith(EXPECTED_START_STRINGS[i])) {
// actual version:
if (file != null)
reader = Tools.getActualReader(file, getFrame());
else
reader = Tools.getActualReader(pReaderCreator.createReader());
break;
}
}
if (reader == null) {
if (pAskUserBeforeUpdate && !Tools.isHeadless()) {
int showResult = new OptionalDontShowMeAgainDialog(
mModeController.getFrame().getJFrame(),
mModeController.getSelectedView(),
"really_convert_to_current_version2",
"confirmation",
mModeController,
new OptionalDontShowMeAgainDialog.StandardPropertyHandler(
mModeController.getController(),
FreeMind.RESOURCES_CONVERT_TO_CURRENT_VERSION),
OptionalDontShowMeAgainDialog.ONLY_OK_SELECTION_IS_STORED)
.show().getResult();
if (showResult != JOptionPane.OK_OPTION) {
throw new IllegalArgumentException(
"We should not open the reader " + pReaderCreator);
}
}
reader = Tools.getUpdateReader(pReaderCreator.createReader(),
FREEMIND_VERSION_UPDATER_XSLT, getFrame());
if (reader == null) {
// something went wrong on update:
// FIXME: Translate me.
this.getModeController()
.getFrame()
.out("Error on conversion. Continue without conversion. Some elements may be lost!");
if (file != null)
reader = Tools.getActualReader(file, getFrame());
else
reader = Tools.getActualReader(pReaderCreator.createReader());
}
}
try {
HashMap IDToTarget = new HashMap();
return (MindMapNodeModel) mModeController.createNodeTreeFromXml(
reader, IDToTarget);
// MindMapXMLElement mapElement = new
// MindMapXMLElement(mModeController);
// mapElement.parseFromReader(reader);
// // complete the arrow links:
// mapElement.processUnfinishedLinks(getLinkRegistry());
// // we wait with "invokeHooksRecursively" until the map is fully
// // registered.
// return (MindMapNodeModel) mapElement.getMapChild();
} catch (Exception ex) {
String errorMessage = "Error while parsing file:" + ex;
System.err.println(errorMessage);
freemind.main.Resources.getInstance().logException(ex);
MindMapXMLElement mapElement = new MindMapXMLElement(
mModeController);
NodeAdapter result = mapElement.createNodeAdapter(getFrame(), null);
result.setText(errorMessage);
return (MindMapNodeModel) result;
} finally {
if (reader != null) {
reader.close();
}
}
}
/**
* Returns pMinimumLength bytes of the files content.
*
* @return an empty string buffer, if something fails.
*/
private StringBuffer readFileStart(Reader pReader, int pMinimumLength) {
BufferedReader in = null;
StringBuffer buffer = new StringBuffer();
try {
// get the file start into the memory:
in = new BufferedReader(pReader);
String str;
while ((str = in.readLine()) != null) {
buffer.append(str);
if (buffer.length() >= pMinimumLength)
break;
}
in.close();
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
return new StringBuffer();
}
return buffer;
}
private void scheduleTimerForAutomaticSaving() {
int numberOfTempFiles = Integer.parseInt(getFrame().getProperty(
"number_of_different_files_for_automatic_save"));
boolean filesShouldBeDeletedAfterShutdown = Resources.getInstance()
.getBoolProperty("delete_automatic_saves_at_exit");
String path = getFrame().getProperty("path_to_automatic_saves");
/* two standard values: */
if (Tools.safeEquals(path, "default")) {
path = null;
}
if (Tools.safeEquals(path, "freemind_home")) {
path = getFrame().getFreemindDirectory();
}
int delay = Integer.parseInt(getFrame().getProperty(
"time_for_automatic_save"));
File dirToStore = null;
if (path != null) {
dirToStore = new File(path);
/* existence? */
if (!dirToStore.isDirectory()) {
dirToStore = null;
System.err.println("Temporary directory " + path
+ " not found. Disabling automatic store.");
delay = Integer.MAX_VALUE;
return;
}
}
timerForAutomaticSaving = new Timer();
timerForAutomaticSaving.schedule(new DoAutomaticSave(
MindMapMapModel.this, numberOfTempFiles,
filesShouldBeDeletedAfterShutdown, dirToStore), delay, delay);
}
private class LockManager extends TimerTask {
File lockedSemaphoreFile = null;
Timer lockTimer = null;
final long lockUpdatePeriod = 4 * 60 * 1000; // four minutes
final long lockSafetyPeriod = 5 * 60 * 1000; // five minutes
String lockingUserOfOldLock = null;
private File getSemaphoreFile(File mapFile) {
return new File(mapFile.getParent()
+ System.getProperty("file.separator") + "$~"
+ mapFile.getName() + "~");
}
public synchronized String popLockingUserOfOldLock() {
String toReturn = lockingUserOfOldLock;
lockingUserOfOldLock = null;
return toReturn;
}
private void writeSemaphoreFile(File inSemaphoreFile) throws Exception {
FileOutputStream semaphoreOutputStream = new FileOutputStream(
inSemaphoreFile);
FileLock lock = null;
try {
lock = semaphoreOutputStream.getChannel().tryLock();
if (lock == null) {
semaphoreOutputStream.close();
System.err.println("Locking failed.");
throw new Exception();
}
} // locking failed
catch (UnsatisfiedLinkError eUle) {
} // This may come with Windows95. We don't insist on detailed
// locking in that case.
catch (NoClassDefFoundError eDcdf) {
} // ^ just like above.
// ^ On Windows95, the necessary libraries are missing.
semaphoreOutputStream.write(System.getProperty("user.name")
.getBytes());
semaphoreOutputStream.write('\n');
semaphoreOutputStream.write(String.valueOf(
System.currentTimeMillis()).getBytes());
semaphoreOutputStream.close();
semaphoreOutputStream = null;
Tools.setHidden(inSemaphoreFile, true, /* synchro= */false); // Exception
// free
if (lock != null)
lock.release();
}
public synchronized String tryToLock(File file) throws Exception {
// Locking should work for opening as well as for saving as.
// We are especially carefull when it comes to exclusivity of
// writing.
File semaphoreFile = getSemaphoreFile(file);
if (semaphoreFile == lockedSemaphoreFile) {
return null;
}
try {
BufferedReader semaphoreReader = new BufferedReader(
new FileReader(semaphoreFile));
String lockingUser = semaphoreReader.readLine();
long lockTime = new Long(semaphoreReader.readLine())
.longValue();
long timeDifference = System.currentTimeMillis() - lockTime;
// catch (NumberFormatException enf) {} // This means that the
// time was not written at all - lock is corrupt
if (timeDifference > lockSafetyPeriod) { // the lock is old
semaphoreReader.close();
lockingUserOfOldLock = lockingUser;
semaphoreFile.delete();
} else
return lockingUser;
} catch (FileNotFoundException e) {
}
writeSemaphoreFile(semaphoreFile);
if (lockTimer == null) {
lockTimer = new Timer();
lockTimer.schedule(this, lockUpdatePeriod, lockUpdatePeriod);
}
releaseLock();
lockedSemaphoreFile = semaphoreFile;
return null;
}
public synchronized void releaseLock() {
if (lockedSemaphoreFile != null) {
lockedSemaphoreFile.delete();
lockedSemaphoreFile = null;
}
} // this may fail, TODO: ensure real deletion
public synchronized void releaseTimer() {
if (lockTimer != null) {
lockTimer.cancel();
lockTimer = null;
}
}
public synchronized void run() { // update semaphore file
if (lockedSemaphoreFile == null) {
System.err
.println("unexpected: lockedSemaphoreFile is null upon lock update");
return;
}
try {
Tools.setHidden(lockedSemaphoreFile, false, /* synchro= */true); // Exception
// free
// ^ We unhide the file before overwriting because JavaRE1.4.2
// does
// not let us open hidden files for writing. This is a
// workaround for Java bug,
// I guess.
writeSemaphoreFile(lockedSemaphoreFile);
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
}
}
}
private class DummyLockManager extends LockManager {
public synchronized String popLockingUserOfOldLock() {
return null;
}
public synchronized String tryToLock(File file) throws Exception {
return null;
}
public synchronized void releaseLock() {
}
public synchronized void releaseTimer() {
}
public synchronized void run() {
}
}
static private class DoAutomaticSave extends TimerTask {
private MindMapMapModel model;
private Vector tempFileStack;
private int numberOfFiles;
private boolean filesShouldBeDeletedAfterShutdown;
private File pathToStore;
/**
* This value is compared with the result of
* getNumberOfChangesSinceLastSave(). If the values coincide, no further
* automatic saving is performed until the value changes again.
*/
private int changeState;
DoAutomaticSave(MindMapMapModel model, int numberOfTempFiles,
boolean filesShouldBeDeletedAfterShutdown, File pathToStore) {
this.model = model;
tempFileStack = new Vector();
numberOfFiles = ((numberOfTempFiles > 0) ? numberOfTempFiles : 1);
this.filesShouldBeDeletedAfterShutdown = filesShouldBeDeletedAfterShutdown;
this.pathToStore = pathToStore;
changeState = model.getNumberOfChangesSinceLastSave();
}
public void run() {
/* Map is dirty enough? */
if (model.getNumberOfChangesSinceLastSave() == changeState)
return;
changeState = model.getNumberOfChangesSinceLastSave();
if (changeState == 0) {
/* map was recently saved. */
return;
}
try {
cancel();
EventQueue.invokeAndWait(new Runnable() {
public void run() {
/* Now, it is dirty, we save it. */
File tempFile;
if (tempFileStack.size() >= numberOfFiles)
tempFile = (File) tempFileStack.remove(0); // pop
else {
try {
tempFile = File.createTempFile(
"FM_"
+ ((model.toString() == null) ? "unnamed"
: model.toString()),
freemind.main.FreeMindCommon.FREEMIND_FILE_EXTENSION,
pathToStore);
if (filesShouldBeDeletedAfterShutdown)
tempFile.deleteOnExit();
} catch (Exception e) {
System.err
.println("Error in automatic MindMapMapModel.save(): "
+ e.getMessage());
freemind.main.Resources.getInstance()
.logException(e);
return;
}
}
try {
model.saveInternal(tempFile, true /* =internal call */);
model.getFrame()
.out(Resources
.getInstance()
.format("automatically_save_message",
new Object[] { tempFile
.toString() }));
} catch (Exception e) {
System.err
.println("Error in automatic MindMapMapModel.save(): "
+ e.getMessage());
freemind.main.Resources.getInstance().logException(
e);
}
tempFileStack.add(tempFile); // add at the back.
}
});
} catch (InterruptedException e) {
freemind.main.Resources.getInstance().logException(e);
} catch (InvocationTargetException e) {
freemind.main.Resources.getInstance().logException(e);
}
}
}
}