/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.module; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import org.openflexo.foundation.rm.FlexoProject; import org.openflexo.foundation.rm.FlexoResource; import org.openflexo.foundation.rm.FlexoResourceData; import org.openflexo.foundation.rm.FlexoStorageResource; import org.openflexo.foundation.rm.SaveResourceException; import org.openflexo.inspector.InspectableObject; import org.openflexo.inspector.InspectorObserver; import org.openflexo.inspector.model.TabModel; import org.openflexo.kvc.KVCObject; import org.openflexo.localization.FlexoLocalization; import org.openflexo.logging.FlexoLogger; import org.openflexo.toolbox.FileUtils; import org.openflexo.view.controller.FlexoController; /** * @author gpolet * */ public class FlexoAutoSaveThread extends Thread { private static final Logger logger = FlexoLogger.getLogger(FlexoAutoSaveThread.class.getPackage().getName()); private static final String AUTO_SAVE_FILE_NAME_INFO = ".autosave"; private static final String AUTO_SAVE_FILE_EXTENSION = ".fas"; // fas stands for FlexoAutoSave protected static final DateFormat formatter = new SimpleDateFormat("dd/MM/yy HH:mm"); /** * The time to sleep between the automatic save operations */ private long sleepTime = 5 * 60 * 1000; // 5 minutes /** * The number of save to perform. */ private int numberOfIntermediateSave = 12; /** * A queue that lists the different files already used. This queue is managed with the FIFO policy. */ private LinkedList<FlexoAutoSaveFile> projects; /** * The project on which this thread works */ private FlexoProject project; /** * The root directory for the specified project containing all the intermediate project save. For example, you will have tempDirectory * that is "C:\Documents and Settings\UserName\Local Settings\Temp\A Flexo Temp Directory" and the <code>projects</code> list will * contain files like: * <ul> * <li>"C:\Documents and Settings\UserName\Local Settings\Temp\A Flexo Temp Directory\A Project.save.1" * <li>"C:\Documents and Settings\UserName\Local Settings\Temp\A Flexo Temp Directory\A Project.save.2" * <li>"C:\Documents and Settings\UserName\Local Settings\Temp\A Flexo Temp Directory\A Project.save.3" * <li>"C:\Documents and Settings\UserName\Local Settings\Temp\A Flexo Temp Directory\A Project.save.4"... * </ul> */ private File tempDirectory; private volatile boolean run = true; /** * */ public FlexoAutoSaveThread(FlexoProject project) { super("Auto-save thread for " + project.getName()); this.project = project; setPriority(Thread.MIN_PRIORITY); setDaemon(true); projects = new LinkedList<FlexoAutoSaveFile>(); initFromFile(); } private File getNextSaveDirectory() { int attempt = 1; File nextSaveFile = null; while (nextSaveFile == null || nextSaveFile.exists()) { nextSaveFile = new File(tempDirectory, project.getName() + AUTO_SAVE_FILE_EXTENSION + "." + attempt); attempt++; } nextSaveFile.mkdirs(); return nextSaveFile; } private File getAutoSafeFileInfo() { return new File(project.getProjectDirectory(), AUTO_SAVE_FILE_NAME_INFO); } /** * */ private void initFromFile() { try { String content = FileUtils.fileContents(getAutoSafeFileInfo()); tempDirectory = new File(content.trim()); if (!tempDirectory.exists() || !content.startsWith(System.getProperty("java.io.tmpdir")) && !content.startsWith(new File(System.getProperty("java.io.tmpdir")).getCanonicalPath())) { tempDirectory = getNewTempDirectory(); } } catch (IOException e) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "IO exception while opening " + AUTO_SAVE_FILE_NAME_INFO + " file.", e); } tempDirectory = getNewTempDirectory(); } File files[] = tempDirectory.listFiles(new FilenameFilter() { /** * Overrides accept * * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) */ @Override public boolean accept(File dir, String name) { return name.indexOf(AUTO_SAVE_FILE_EXTENSION) > -1; } }); if (files != null) { for (File file : files) { if (file.isDirectory()) { projects.add(new FlexoAutoSaveFile(file, new Date(file.lastModified()))); } } } Collections.sort(projects, new Comparator<FlexoAutoSaveFile>() { // This comparator will make oldest files first and newer ones last // in the queue /** * Overrides compare * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override public int compare(FlexoAutoSaveFile o1, FlexoAutoSaveFile o2) { if (o1.lastModified() < o2.lastModified()) { return -1; } else if (o1.lastModified() > o2.lastModified()) { return 1; } else { return 0; } } }); } /** * @return */ private File getNewTempDirectory() { tempDirectory = null; File tmpdir = new File(System.getProperty("java.io.tmpdir")); try { tmpdir = tmpdir.getCanonicalFile(); } catch (IOException e2) { e2.printStackTrace(); } int attempt = 0; while (tempDirectory == null || tempDirectory.exists()) { tempDirectory = new File(tmpdir, project.getName() + (attempt > 0 ? attempt : "")); attempt++; } try { tempDirectory = tempDirectory.getCanonicalFile(); } catch (IOException e1) { e1.printStackTrace(); } tempDirectory.mkdirs(); try { FileUtils.saveToFile(getAutoSafeFileInfo(), tempDirectory.getAbsolutePath()); } catch (IOException e1) { e1.printStackTrace(); } return tempDirectory; } /** * Overrides run * * @see java.lang.Thread#run() */ @Override public void run() { while (run) { pauseIfNeeded(); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "I got interrupted, probably because the user has changed the sleep time", e); } continue; } pauseIfNeeded(); boolean needsSave = false; if (projects.size() == 0) { needsSave = true; } else { Date lastAutoSave = projects.getLast().getCreationDate(); for (FlexoResource<? extends FlexoResourceData> resource : project) { if (resource instanceof FlexoStorageResource && resource.getLastUpdate().after(lastAutoSave)) { needsSave = true; break; } } } if (!needsSave) { if (logger.isLoggable(Level.INFO)) { logger.info("project has not changed since last auto-save: " + formatter.format(projects.getLast().getCreationDate())); } continue; } try { boolean saveActionSuccess = true; File nextSaveDirectory = getNextSaveDirectory(); try { project.saveAs(nextSaveDirectory, null, null, false, true); } catch (SaveResourceException e) { saveActionSuccess = false; e.printStackTrace(); } catch (Exception e) { saveActionSuccess = false; e.printStackTrace(); } if (saveActionSuccess) { projects.add(new FlexoAutoSaveFile(nextSaveDirectory, new Date())); } else { SwingUtilities.invokeLater(new AutoSaveActionFailed()); } if (projects.size() >= numberOfIntermediateSave && numberOfIntermediateSave > 0) { while (projects.size() > numberOfIntermediateSave) { FlexoAutoSaveFile toRemove = projects.removeFirst();// First in First out policy FileUtils.deleteDir(toRemove.getDirectory()); } } } catch (Exception e) { e.printStackTrace(); } } } private void pauseIfNeeded() { while (pause) { synchronized (this) { try { wait(); } catch (InterruptedException e) { } } } } public FlexoProject getProject() { return project; } public int getNumberOfIntermediateSave() { return numberOfIntermediateSave; } public void setNumberOfIntermediateSave(int numberOfIntermediateSave) { this.numberOfIntermediateSave = numberOfIntermediateSave; } public long getSleepTime() { return sleepTime; } public void setSleepTime(long sleepTime) { this.sleepTime = sleepTime; if (getState() == State.TIMED_WAITING) { this.interrupt(); } } /** * */ @SuppressWarnings("unchecked") public List<File> getSavedFiles() { return (List<File>) projects.clone(); } private boolean autoSaveFailedNotified = false; private volatile boolean pause = false; private class AutoSaveActionFailed implements Runnable { /** * Overrides run * * @see java.lang.Runnable#run() */ @Override public void run() { if (autoSaveFailedNotified) { return; } FlexoController.showError( FlexoLocalization.localizedForKey("auto_save_action_failed"), FlexoLocalization.localizedForKey("auto_save_action_could_not_be_performed") + "\n" + FlexoLocalization .localizedForKey("verify_that_your_disk_is_not_full_and_that_you_can_write_in_the_temp_directory.")); autoSaveFailedNotified = true; } } public static class FlexoAutoSaveFile extends KVCObject implements InspectableObject { private File directory; private Date creationDate; /** * */ public FlexoAutoSaveFile(File directory, Date creationDate) { this.directory = directory; this.creationDate = creationDate; } public String getName() { return directory.getName(); } public long lastModified() { return creationDate.getTime(); } public File getDirectory() { return directory; } public String getPath() { return getDirectory().getAbsolutePath(); } public Date getCreationDate() { return creationDate; } public String getCreationDateAsAString() { return formatter.format(creationDate); } public String getOffset() { long current = System.currentTimeMillis(); long offset = current - creationDate.getTime(); if (offset < 60 * 60 * 1000) { return FlexoLocalization.localizedForKeyWithParams("($minutes) minutes_ago", this); } else if (offset < 24 * 60 * 60 * 1000) { return FlexoLocalization.localizedForKeyWithParams("($hours) hours_ago_and_($minutesOverHours)_minutes", this); } else { return formatter.format(creationDate); } } public String minutes() { long offset = System.currentTimeMillis() - creationDate.getTime(); return String.valueOf(Math.round((float) offset / (60 * 1000))); } public String minutesOverHours() { long offset = System.currentTimeMillis() - creationDate.getTime(); return String.valueOf(offset / (60 * 1000) % 60); } public String hours() { long offset = System.currentTimeMillis() - creationDate.getTime(); return String.valueOf(Math.round((float) offset / (60 * 60 * 1000))); } /** * Overrides addInspectorObserver * * @see org.openflexo.inspector.InspectableObject#addInspectorObserver(org.openflexo.inspector.InspectorObserver) */ @Override public void addInspectorObserver(InspectorObserver obs) { } /** * Overrides deleteInspectorObserver * * @see org.openflexo.inspector.InspectableObject#deleteInspectorObserver(org.openflexo.inspector.InspectorObserver) */ @Override public void deleteInspectorObserver(InspectorObserver obs) { } /** * Overrides getInspectorName * * @see org.openflexo.inspector.InspectableObject#getInspectorName() */ @Override public String getInspectorName() { return null; } /** * Overrides isDeleted * * @see org.openflexo.inspector.InspectableObject#isDeleted() */ @Override public boolean isDeleted() { return false; } @Override public String getInspectorTitle() { return null; } @Override public Vector<TabModel> inspectionExtraTabs() { // TODO Auto-generated method stub return null; } } public void setRun(boolean run) { this.run = run; } public void pause() { pause = true; } public void unpause() { pause = false; notify(); } public File getTempDirectory() { return tempDirectory; } public LinkedList<FlexoAutoSaveFile> getProjects() { return projects; } }