package org.korsakow.ide.ui.controller.action; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.prefs.Preferences; import javax.swing.JMenuItem; import javax.swing.Timer; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPathExpressionException; import org.apache.log4j.Logger; import org.dsrg.soenea.domain.MapperException; import org.dsrg.soenea.domain.command.CommandException; import org.dsrg.soenea.environment.CreationException; import org.dsrg.soenea.environment.KeyNotFoundException; import org.korsakow.domain.CommandExecutor; import org.korsakow.domain.Settings; import org.korsakow.domain.Settings.AdjustFilenames; import org.korsakow.domain.command.AdjustToAbsolutePathsCommand; import org.korsakow.domain.command.AdjustToRelativeOrAbsolutePathsCommand; import org.korsakow.domain.command.AdjustToRelativePathsCommand; import org.korsakow.domain.command.Helper; import org.korsakow.domain.command.Request; import org.korsakow.domain.interf.IProject; import org.korsakow.domain.interf.ISettings; import org.korsakow.domain.mapper.input.ProjectInputMapper; import org.korsakow.domain.mapper.input.SettingsInputMapper; import org.korsakow.ide.Application; import org.korsakow.ide.DataRegistry; import org.korsakow.ide.lang.LanguageBundle; import org.korsakow.ide.resources.media.JSound; import org.korsakow.ide.resources.media.Playable; import org.korsakow.ide.ui.ProjectExplorer.Action; import org.korsakow.ide.ui.components.tree.FolderNode; import org.korsakow.ide.util.PreferencesManager; import org.korsakow.ide.util.UIResourceManager; import org.korsakow.ide.util.UIUtil; import org.korsakow.ide.util.Util; import org.w3c.dom.Element; public class SaveProjectAction extends AbstractAction { /** * Keeps a reference to the playable until it finishes playing (so that it won't be GC'd prematurely). Also disposes the resource when its done. * Note that this relies on the playable's duration. * * @author d * */ public static class PlayableCloser implements ActionListener { private static Set<PlayableCloser> strongReferenceMap = new HashSet<PlayableCloser>(); private Timer timer; private Playable playable; public PlayableCloser(Playable playable) { this.playable = playable; strongReferenceMap.add(this); timer = new Timer((int)(playable.getDuration() + 100), this); // the 100 is just to make sure it finishes timer.start(); } public void actionPerformed(ActionEvent event) { strongReferenceMap.remove(this); timer.stop(); timer.removeActionListener(this); playable.dispose(); playable = null; timer = null; } } public SaveProjectAction() { } @Override public void performAction() { if (Application.getInstance().getSaveFile() == null) { new SaveProjectAsAction().performAction(); return; } try{ if (!saveProject(Application.getInstance().getSaveFile())) return; String filename = DataRegistry.getFile().getAbsolutePath(); addRecent(filename); Playable sound = new JSound(UIResourceManager.getSoundResourceStream(UIResourceManager.SOUND_SAVE)); sound.start(); new PlayableCloser(sound); } catch (Exception e) { Application.getInstance().showUnhandledErrorDialog(LanguageBundle.getString("general.errors.cantsave.title"), e); } } public static boolean saveProject(File file) throws SQLException, KeyNotFoundException, CreationException, MapperException, XPathExpressionException, IOException, TransformerException { System.gc(); try { if (!adjustFilenames(file)) return false; } catch (CommandException e) { if (e.getCause() instanceof FileNotFoundException) { Logger.getLogger(SaveProjectAction.class).error("", e); Application.getInstance().showAlertDialog(LanguageBundle.getString("general.errors.cantadjustpath.title"), LanguageBundle.getString("general.errors.cantadjustpath.message")); } else { Logger.getLogger(SaveProjectAction.class).error("", e); Application.getInstance().showUnhandledErrorDialog(LanguageBundle.getString("general.errors.cantadjustpath.title"), e); } } catch (Exception e) { Application.getInstance().showUnhandledErrorDialog(LanguageBundle.getString("general.errors.cantadjustpath.title"), e); } FolderNode resourceRoot = Application.getInstance().getProjectExplorer().getResourceBrowser().getResourceTreeTable().getRootNode(); Element resourcesNode = UIUtil.resourceTreeToDom(DataRegistry.getDocument(), resourceRoot); DataRegistry.getHelper().removeNodes("/korsakow/resources"); DataRegistry.getHelper().xpathAsElement("/korsakow").appendChild(resourcesNode); DataRegistry.setFile(file); DataRegistry.flush(); Application.getInstance().setSaveFile(file, DataRegistry.getHeadVersion()); System.gc(); return true; } private static boolean adjustFilenames(File file) throws CommandException, MapperException, InterruptedException { IProject project = ProjectInputMapper.find(); ISettings settings = SettingsInputMapper.find(); AdjustFilenames adjustFilenames = AdjustFilenames.Disabled; try { adjustFilenames = AdjustFilenames.fromId(settings.getString(Settings.AdjustFilenamesOnSave)); } catch (Exception e) { Logger.getLogger(SaveProjectAction.class).error("", e); } Request request = new Request(); Helper response = null; switch (adjustFilenames) { case Disabled: break; case Absolute: request.set("id", project.getId()); response = CommandExecutor.executeCommand(AdjustToAbsolutePathsCommand.class, request); break; case Relative: request.set("id", project.getId()); request.set("basePath", file.getParentFile().getAbsolutePath()); response = CommandExecutor.executeCommand(AdjustToRelativePathsCommand.class, request); if (response.get("status") == Boolean.TRUE) { } else { Application.getInstance().showAlertDialog(LanguageBundle.getString("general.errors.cantadjustrelativepath.title"), LanguageBundle.getString("general.errors.cantadjustrelativepath.message")); return false; } break; case Smart: request.set("id", project.getId()); request.set("basePath", file.getParentFile().getAbsolutePath()); response = CommandExecutor.executeCommand(AdjustToRelativeOrAbsolutePathsCommand.class, request); break; } return true; } public static void addRecent(String value) { List<String> recent = getRecent(); if (recent.contains(value)) // bump recent.remove(value); recent.add(0, value); setRecent(recent); } public static List<String> getRecent() { Preferences recentPrefs = PreferencesManager.getPreferences(SaveProjectAction.class).node("recent"); String recentStr = recentPrefs.get("recent", ""); List<String> recent = recentStr.length()>0?Util.asList(recentStr.split(File.pathSeparator)):new ArrayList<String>(); return recent; } public static void setRecent(List<String> recent) { while (recent.size() > 5) { recent.remove(recent.get(recent.size()-1)); } recent = new ArrayList<String>(recent); Preferences recentPrefs = PreferencesManager.getPreferences(SaveProjectAction.class).node("recent"); String recentStr = Util.join(recent, File.pathSeparator); recentPrefs.put("recent", recentStr); Application app = Application.getInstance(); app.getProjectExplorer().getMenu(Action.MenuFileRecent).removeAll(); for (String item : recent) { File file = new File(item); if (!file.canRead()) continue; JMenuItem menuItem = new JMenuItem(item); menuItem.addActionListener(new LoadRecentProjectAction()); app.getProjectExplorer().getMenu(Action.MenuFileRecent).add(menuItem); } } }