/* * Copyright (C) 2007 Rob Manning * manningr@users.sourceforge.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.sourceforge.squirrel_sql.client.update.gui.installer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; import net.sourceforge.squirrel_sql.client.update.UpdateUtil; import net.sourceforge.squirrel_sql.client.update.gui.ArtifactStatus; import net.sourceforge.squirrel_sql.client.update.gui.installer.event.InstallStatusListener; import net.sourceforge.squirrel_sql.client.update.gui.installer.event.InstallStatusListenerImpl; import net.sourceforge.squirrel_sql.client.update.xmlbeans.ChangeListXmlBean; import net.sourceforge.squirrel_sql.fw.util.FileWrapper; import net.sourceforge.squirrel_sql.fw.util.IOUtilities; import net.sourceforge.squirrel_sql.fw.util.ScriptLineFixer; import net.sourceforge.squirrel_sql.fw.util.StringManager; import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory; import net.sourceforge.squirrel_sql.fw.util.Utilities; import net.sourceforge.squirrel_sql.fw.util.log.ILogger; import net.sourceforge.squirrel_sql.fw.util.log.LoggerController; import org.springframework.beans.factory.annotation.Required; /** * This is a bean that the prelaunch app uses. The pre-launch app main class (PreLaunchUpdateApplication) * loads the spring context, and therefore can't managed by spring. So, it is very small and most of it's * logic for doing updates resides here. * * @author manningr */ public class PreLaunchHelperImpl implements PreLaunchHelper { /** The message we show the user in the update dialog that is shown when there are updates to install */ private String INSTALL_UPDATES_MESSAGE; /** * The title of the dialect that we show the user in the update dialog that is shown when there are updates * to install */ private String INSTALL_UPDATES_TITLE; private String RESTORE_FROM_BACKUP_TITLE; private String RESTORE_FROM_BACKUP_MESSAGE; private String RESTORE_FAILED_MESSAGE; private String BACKUP_FAILED_MESSAGE; private String INSTALL_FAILED_MESSAGE; /** Internationalized strings for this class */ private StringManager s_stringMgr; /** Logger for this class. */ private ILogger s_log; /** Used to override logic for calculating script location for testing purposes */ private String scriptLocation = null; /* --------------------------- Spring=injected dependencies --------------------------------------------*/ /* Spring-injected */ private UpdateUtil updateUtil = null; @Required public void setUpdateUtil(UpdateUtil util) { Utilities.checkNull("setUpdateUtil", "util", util); this.updateUtil = util; } /* Spring-injected */ private ArtifactInstallerFactory artifactInstallerFactory = null; @Required public void setArtifactInstallerFactory(ArtifactInstallerFactory artifactInstallerFactory) { Utilities.checkNull("setArtifactInstallerFactory", "artifactInstallerFactory", artifactInstallerFactory); this.artifactInstallerFactory = artifactInstallerFactory; } /* Spring-injected */ private List<ScriptLineFixer> scriptLineFixers = null; @Required public void setScriptLineFixers(List<ScriptLineFixer> scriptLineFixers) { Utilities.checkNull("setScriptLineFixers", "scriptLineFixers", scriptLineFixers); this.scriptLineFixers = scriptLineFixers; } /* Spring-injected */ private IOUtilities ioutils = null; /** * @param ioutils * the ioutils to set */ @Required public void setIoutils(IOUtilities ioutils) { Utilities.checkNull("setIoutils", "ioutils", ioutils); this.ioutils = ioutils; } /* ----------------------------------- Public API ------------------------------------------------------*/ public PreLaunchHelperImpl() throws IOException { s_log = LoggerController.createLogger(PreLaunchHelperImpl.class); s_stringMgr = StringManagerFactory.getStringManager(PreLaunchHelperImpl.class); // i18n[PreLaunchHelperImpl.installUpdatesTitle=Updates Available] INSTALL_UPDATES_TITLE = s_stringMgr.getString("PreLaunchHelperImpl.installUpdatesTitle"); // i18n[PreLaunchHelperImpl.installUpdatesMessage=Updates are ready to be installed. Install them now?] INSTALL_UPDATES_MESSAGE = s_stringMgr.getString("PreLaunchHelperImpl.installUpdatesMessage"); // i18n[PreLaunchHelperImpl.restoreFromBackupTitle=Confirm Restore From Backup RESTORE_FROM_BACKUP_TITLE = s_stringMgr.getString("PreLaunchHelperImpl.restoreFromBackupTitle"); // i18n[PreLaunchHelperImpl.restoreFromBackupMessage=Restore SQuirreL to previous version before // last update?] RESTORE_FROM_BACKUP_MESSAGE = s_stringMgr.getString("PreLaunchHelperImpl.restoreFromBackupMessage"); // i18n[PreLaunchHelperImpl.backupFailedMessage=Backup of existing files failed. Installation cannot // proceed] BACKUP_FAILED_MESSAGE = s_stringMgr.getString("PreLaunchHelperImpl.backupFailedMessage"); // i18n[PreLaunchHelperImpl.installFailedMessage=Unexpected error while attempting to install updates] INSTALL_FAILED_MESSAGE = s_stringMgr.getString("PreLaunchHelperImpl.installFailedMessage"); // i18n[PreLaunchHelperImpl.restoreFailedMessage=Restore from backup failed. Re-installation may be // required. RESTORE_FAILED_MESSAGE = s_stringMgr.getString("PreLaunchHelperImpl.restoreFailedMessage"); } /** * @see net.sourceforge.squirrel_sql.client.update.gui.installer.PreLaunchHelper#installUpdates(boolean) */ @Override public void installUpdates(boolean prompt) { FileWrapper changeListFile = updateUtil.getChangeListFile(); if (hasChangesToBeApplied(changeListFile)) { logInfo("Pre-launch update app detected a changeListFile to be processed"); if (prompt) { if (showConfirmDialog(INSTALL_UPDATES_MESSAGE, INSTALL_UPDATES_TITLE)) { installUpdates(changeListFile); } else { logInfo("User cancelled update installation"); } } else { installUpdates(changeListFile); } } } /** * Updates the launch script with changes made necessary by the new release. * * @throws IOException * if an I/O error occurs */ @Override public void updateLaunchScript() throws IOException { // 1. determine which script(s) to fix. List<String> launchScriptLocations = getLaunchScriptLocations(); for (String launchScript : launchScriptLocations) { logInfo("Applying updates to launch script: " + launchScript); // 2. Get the lines from the file, applying the line fixers List<String> lines = ioutils.getLinesFromFile(launchScript, scriptLineFixers); // 3. Write the fixed lines back out to the file. ioutils.writeLinesToFile(launchScript, lines); } } private List<String> getLaunchScriptLocations() { List<String> result = new ArrayList<String>(); if (scriptLocation != null) { result.add(scriptLocation); return result; } String os = System.getProperty("os.name"); if (os != null && os.toLowerCase().startsWith("windows")) { result.add("squirrel-sql.bat"); // And for cygwin users on Windows result.add("squirrel-sql.sh"); } else if (os != null && os.toLowerCase().startsWith("mac")) { // Java on Mac OS X doesn't find squirrel-sql.sh; so construct the absolute path. result.add(getMacOSContentsPath() + "/MacOS/squirrel-sql.sh"); } else { result.add("squirrel-sql.sh"); } return result; } private String getMacOSContentsPath() { // The user.dir property on the Mac is /Applications/SQuirreLSQL.app/Contents/Resources/Java String userdir = System.getProperty("user.dir"); String[] parts = userdir.split("Contents"); return parts[0] + "/Contents"; } @Override public void copySplashImage() throws IOException { // The user.dir property on the Mac is /Applications/SQuirreLSQL.app/Contents/Resources/Java String squirrelHome = System.getProperty("user.dir"); String jarFilename = squirrelHome + "/update/downloads/core/squirrel-sql.jar"; String resourceName = "splash.jpg"; String pathToIconsDir = squirrelHome + "/icons"; String destinationFile = pathToIconsDir + "/" + resourceName; File iconsDir = new File(pathToIconsDir); if (!iconsDir.exists()) { logInfo("Icons directory ("+pathToIconsDir+") doesn't exist, so attempting to create it."); boolean result = iconsDir.mkdir(); if (!result) { s_log.error("Failed to create icons directory ("+pathToIconsDir+")"); } } logInfo("Copying splash.jpg from jarfile ("+jarFilename+") to "+destinationFile); ioutils.copyResourceFromJarFile(jarFilename, resourceName, destinationFile); } /** * @see net.sourceforge.squirrel_sql.client.update.gui.installer.PreLaunchHelper#restoreFromBackup() */ @Override public void restoreFromBackup() { if (showConfirmDialog(RESTORE_FROM_BACKUP_MESSAGE, RESTORE_FROM_BACKUP_TITLE)) { try { FileWrapper backupDir = updateUtil.getBackupDir(); FileWrapper changeListFile = updateUtil.getFile(backupDir, UpdateUtil.CHANGE_LIST_FILENAME); ChangeListXmlBean changeList = updateUtil.getChangeList(changeListFile); ArtifactInstaller installer = artifactInstallerFactory.create(changeList, null); if (!installer.restoreBackupFiles()) { showErrorDialog(RESTORE_FAILED_MESSAGE); s_log.error("restoreFromBackup: " + RESTORE_FAILED_MESSAGE); } } catch (Throwable e) { s_log.error("Unexpected error while attempting restore from backup: " + e.getMessage(), e); showErrorDialog(RESTORE_FAILED_MESSAGE); } } shutdown("Pre-launch update app finished"); } /* ------------------------------------- Helper methods ------------------------------------------------*/ /** * Peeks into the changelist file to see if there are artifacts to change. This is precautionary as the GUI * should prevent the changeList file from being created if there are no artifacts to be changed. * * @param changeListFile * the file that contains the changeList * @return true if the changeListFile exists and has changes to be applied; false otherwise. */ private boolean hasChangesToBeApplied(final FileWrapper changeListFile) { boolean result = false; try { if (changeListFile.exists()) { final ChangeListXmlBean changeListBean = updateUtil.getChangeList(changeListFile); final List<ArtifactStatus> changeList = changeListBean.getChanges(); final int changeListSize = changeList.size(); logInfo("hasChangesToBeApplied: changeListFile (" + changeListSize + ") has " + changeListSize + " changes to be applied"); if (changeList != null && changeListSize > 0) { result = true; } else { logInfo("Aborting update: changeList was found with no updates"); } } else { logInfo("installUpdates: changeList file (" + changeListFile + ") doesn't exist"); } } catch (FileNotFoundException e) { s_log.error("hasChangesToBeApplied: unable to get change list from file (" + changeListFile + "): " + e.getMessage()); } return result; } /** * Shuts down this small pre-launch helper application. */ private void shutdown(String message) { logInfo(message); LoggerController.shutdown(); System.exit(0); } /** * Install the updates, taking care to backup the originals first. * * @param changeList * the xml file describing the changes to be made. * @throws Exception * if any error occurs */ private void installUpdates(final FileWrapper changeList) { Thread t = new Thread(new Runnable() { public void run() { try { ProgressDialogController controller = new ProgressDialogControllerImpl(); InstallStatusListener listener = new InstallStatusListenerImpl(controller); ArtifactInstaller installer = artifactInstallerFactory.create(changeList, listener); if (installer.backupFiles()) { installer.installFiles(); } else { showErrorDialog(BACKUP_FAILED_MESSAGE); } controller.hideProgressDialog(); shutdown("Pre-launch update app finished"); } catch (Throwable e) { String message = INSTALL_FAILED_MESSAGE + ": " + e.getMessage(); s_log.error(message, e); showErrorDialog(message); } } }); t.setName("Update Installer Thread"); t.start(); } /** * Ask the user a question * * @param message * the question to ask * @param title * the title of the dialog * @return true if they said YES; false otherwise. */ private boolean showConfirmDialog(String message, String title) { int choice = JOptionPane.showConfirmDialog(null, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); return choice == JOptionPane.YES_OPTION; } /** * Show the user an error dialog. * * @param message * the message to give in the dialog. */ private void showErrorDialog(String message) { JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE); } private void logInfo(String message) { if (s_log.isInfoEnabled()) { s_log.info(message); } } /** * @param scriptLocation * the scriptLocation to set */ public void setScriptLocation(String scriptLocation) { this.scriptLocation = scriptLocation; } /** * @return the scriptLocation */ public String getScriptLocation() { return scriptLocation; } }