/*
* 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;
}
}