/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.update; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.Box; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import icy.file.FileUtil; import icy.gui.frame.ActionFrame; import icy.gui.frame.progress.AnnounceFrame; import icy.gui.frame.progress.CancelableProgressFrame; import icy.gui.frame.progress.DownloadFrame; import icy.gui.frame.progress.FailedAnnounceFrame; import icy.gui.frame.progress.ProgressFrame; import icy.gui.util.GuiUtil; import icy.main.Icy; import icy.network.NetworkUtil; import icy.network.URLUtil; import icy.preferences.ApplicationPreferences; import icy.system.SystemUtil; import icy.system.thread.ThreadUtil; import icy.update.ElementDescriptor.ElementFile; import icy.util.StringUtil; /** * @author stephane */ public class IcyUpdater { private final static int ANNOUNCE_SHOWTIME = 15; public final static String PARAM_ARCH = "arch"; public final static String PARAM_VERSION = "version"; // internals static boolean wantUpdate = false; private static boolean silent; private static boolean updating = false; private static boolean checking = false; private static ActionFrame frame = null; private static Runnable checker = new Runnable() { @Override public void run() { processCheckUpdate(); } }; public static boolean getWantUpdate() { return wantUpdate; } /** * return true if we are currently checking for update */ public static boolean isCheckingForUpdate() { return checking || ThreadUtil.hasWaitingBgSingleTask(checker); } /** * return true if we are currently processing update */ public static boolean isUpdating() { return isCheckingForUpdate() || ((frame != null) && frame.isVisible()) || updating; } /** * Do the check update process */ public static void checkUpdate(boolean silent) { if (!isUpdating()) { IcyUpdater.silent = silent; ThreadUtil.bgRunSingle(checker); } } /** * @deprecated Use {@link #checkUpdate(boolean)} instead */ @Deprecated public static void checkUpdate(boolean showProgress, boolean auto) { checkUpdate(!showProgress || auto); } /** * Check for application update process (synchronized method) */ public static synchronized void processCheckUpdate() { checking = true; try { wantUpdate = false; // delete update directory to avoid partial update FileUtil.delete(Updater.UPDATE_DIRECTORY, true); final ArrayList<ElementDescriptor> toUpdate; final ProgressFrame checkingFrame; if (!silent && !Icy.getMainInterface().isHeadLess()) checkingFrame = new CancelableProgressFrame("checking for application update..."); else checkingFrame = null; final String params = PARAM_ARCH + "=" + SystemUtil.getOSArchIdString() + "&" + PARAM_VERSION + "=" + Icy.version.toShortString(); try { // error (or cancel) while downloading XML ? if (!downloadAndSaveForUpdate( ApplicationPreferences.getUpdateRepositoryBase() + ApplicationPreferences.getUpdateRepositoryFile() + "?" + params, Updater.UPDATE_NAME, checkingFrame, !silent)) { // remove partially downloaded files FileUtil.delete(Updater.UPDATE_DIRECTORY, true); return; } // check if some elements need to be updated from network toUpdate = Updater.getUpdateElements(Updater.getLocalElements()); } finally { if (checkingFrame != null) checkingFrame.close(); } final boolean needUpdate; // empty ? --> no update if (toUpdate.isEmpty()) needUpdate = false; // only the updater require updates ? --> no update else if ((toUpdate.size() == 1) && (toUpdate.get(0).getName().equals(Updater.ICYUPDATER_NAME))) needUpdate = false; // otherwise --> update else needUpdate = true; // some elements need to be updated ? if (needUpdate) { // silent update or headless mode if (silent || Icy.getMainInterface().isHeadLess()) { // automatically install updates if (prepareUpdate(toUpdate, true)) // we want update when application will exit wantUpdate = true; } else { final String mess; if (toUpdate.size() > 1) mess = "Some updates are available..."; else mess = "An update is available..."; // show announcement for 15 seconds new AnnounceFrame(mess, "View", new Runnable() { @Override public void run() { // display updates and process them if user accept showUpdateAndProcess(toUpdate); } }, ANNOUNCE_SHOWTIME); } } else { // cleanup FileUtil.delete(Updater.UPDATE_DIRECTORY, true); // inform that there is no update available if (!silent && !Icy.getMainInterface().isHeadLess()) new AnnounceFrame("No application update available", 10); } } finally { checking = false; } } static void showUpdateAndProcess(final ArrayList<ElementDescriptor> elements) { if (frame != null) { synchronized (frame) { if (frame.isVisible()) return; frame.getMainPanel().removeAll(); } } else frame = new ActionFrame("Application update", true); frame.setPreferredSize(new Dimension(640, 500)); frame.getOkBtn().setText("Install"); frame.setOkAction(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ThreadUtil.bgRun(new Runnable() { @Override public void run() { // download required files if (prepareUpdate(elements, true)) { // ask to update and restart application now wantUpdate = true; Icy.confirmRestart(); } else new FailedAnnounceFrame("An error occured while downloading files (see details in console)", 10000); } }); } }); final JPanel topPanel = GuiUtil.createPageBoxPanel(Box.createVerticalStrut(4), GuiUtil.createCenteredBoldLabel("The following(s) element(s) will be updated"), Box.createVerticalStrut(4)); final JTextArea changeLogArea = new JTextArea(); changeLogArea.setEditable(false); final JLabel changeLogTitleLabel = GuiUtil.createBoldLabel("Change log :"); final JList list = new JList(elements.toArray()); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (list.getSelectedValue() != null) { final ElementDescriptor element = (ElementDescriptor) list.getSelectedValue(); final String changeLog = element.getChangelog(); if (StringUtil.isEmpty(changeLog)) changeLogArea.setText("no change log"); else changeLogArea.setText(element.getChangelog()); changeLogArea.setCaretPosition(0); changeLogTitleLabel.setText(element.getName() + " change log"); } } }); list.setSelectedIndex(0); final JScrollPane medScrollPane = new JScrollPane(list); final JScrollPane changeLogScrollPane = new JScrollPane(GuiUtil.createTabArea(changeLogArea, 4)); final JPanel bottomPanel = GuiUtil.createPageBoxPanel(Box.createVerticalStrut(4), GuiUtil.createCenteredLabel(changeLogTitleLabel), Box.createVerticalStrut(4), changeLogScrollPane); final JPanel mainPanel = frame.getMainPanel(); final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, medScrollPane, bottomPanel); mainPanel.add(topPanel, BorderLayout.NORTH); mainPanel.add(splitPane, BorderLayout.CENTER); frame.pack(); frame.addToDesktopPane(); frame.setVisible(true); frame.center(); frame.requestFocus(); // set splitter to middle splitPane.setDividerLocation(0.5d); } static boolean prepareUpdate(List<ElementDescriptor> elements, boolean showProgress) { final DownloadFrame downloadingFrame; updating = true; if (showProgress && !Icy.getMainInterface().isHeadLess()) downloadingFrame = new DownloadFrame(""); else downloadingFrame = null; try { // get total number of files to process int numFile = 0; for (ElementDescriptor element : elements) numFile += element.getFiles().size(); if (downloadingFrame != null) downloadingFrame.setLength(numFile); int curFile = 0; for (ElementDescriptor element : elements) { for (ElementFile elementFile : element.getFiles()) { curFile++; if (downloadingFrame != null) { // update progress frame message and position downloadingFrame.setMessage("Downloading updates " + curFile + " / " + numFile); final String toolTip = "Downloading " + element.getName() + " : " + FileUtil.getFileName(elementFile.getLocalPath()); // update progress frame tooltip downloadingFrame.setToolTipText(toolTip); } // symbolic link file ? if (elementFile.isLink()) { // special treatment if (!FileUtil.createLink( Updater.UPDATE_DIRECTORY + FileUtil.separator + elementFile.getLocalPath(), elementFile.getOnlinePath())) { // remove partially downloaded files FileUtil.delete(Updater.UPDATE_DIRECTORY, true); return false; } } else { // local file need to be updated --> download new file if (Updater.needUpdate(elementFile.getLocalPath(), elementFile.getDateModif())) { // error (or cancel) while downloading ? if (!downloadAndSaveForUpdate( URLUtil.getNetworkURLString(ApplicationPreferences.getUpdateRepositoryBase(), elementFile.getOnlinePath()), elementFile.getLocalPath(), downloadingFrame, showProgress)) { // remove partially downloaded files FileUtil.delete(Updater.UPDATE_DIRECTORY, true); return false; } } } } } } finally { if (downloadingFrame != null) downloadingFrame.close(); updating = false; } return true; } public static boolean downloadAndSaveForUpdate(String downloadPath, String savePath, ProgressFrame frame, boolean displayError) { // get data final byte[] data; data = NetworkUtil.download(downloadPath, frame, displayError); if (data == null) return false; // build save filename String saveFilename = Updater.UPDATE_DIRECTORY + FileUtil.separator; if (StringUtil.isEmpty(savePath)) saveFilename += URLUtil.getURLFileName(downloadPath, true); else saveFilename += savePath; if (!FileUtil.save(saveFilename, data, displayError)) return false; return true; } /** * Return true if required files for updates are present */ private static boolean canDoUpdate() { // check for updater presence boolean requiredFilesExist = FileUtil .exists(FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + Updater.UPDATER_NAME); // // in update directory ? // requiredFilesExist |= FileUtil.exists(Updater.UPDATE_DIRECTORY + FileUtil.separator + // Updater.UPDATER_NAME); // check for update xml file requiredFilesExist &= FileUtil.exists(Updater.UPDATE_DIRECTORY + FileUtil.separator + Updater.UPDATE_NAME); // required files present so we can do update return requiredFilesExist; } /** * Launch the updater with the specified update and restart parameters */ public static boolean launchUpdater(boolean doUpdate, boolean restart) { if (doUpdate) { final String updateName = Updater.UPDATE_DIRECTORY + FileUtil.separator + Updater.UPDATER_NAME; // updater need update ? process it first if (FileUtil.exists(updateName)) { // replace updater if (!FileUtil.rename(updateName, FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + Updater.UPDATER_NAME, true)) { System.err.println("Can't update 'Upater.jar', Update process can't continue."); return false; } } // this is not really needed... if (!canDoUpdate()) { System.err.println("Can't process update : some required files are missing."); return false; } } String params = ""; if (doUpdate) params += Updater.ARG_UPDATE + " "; if (!restart) params += Updater.ARG_NOSTART + " "; // launch updater // WARNING: don't use application folder here, it doesn't work as expected ! SystemUtil.execJAR(Updater.UPDATER_NAME, params); // you have to exit application then... return true; } }