/**
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.sparkimpl.updater;
import com.thoughtworks.xstream.XStream;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.protocol.Protocol;
import org.jivesoftware.Spark;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.component.ConfirmDialog;
import org.jivesoftware.spark.component.ConfirmDialog.ConfirmListener;
import org.jivesoftware.spark.component.TitlePanel;
import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.ByteFormat;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.settings.JiveInfo;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.TimerTask;
public class CheckUpdates {
private String mainUpdateURL;
private JProgressBar bar;
private TitlePanel titlePanel;
private boolean downloadComplete = false;
private boolean cancel = false;
public static boolean UPDATING = false;
private boolean sparkPluginInstalled;
private XStream xstream = new XStream();
private String sizeText;
public CheckUpdates() {
// Set the Jabber IQ Provider for Jabber:iq:spark
ProviderManager.getInstance().addIQProvider("query", "jabber:iq:spark", new SparkVersion.Provider());
// For simplicity, use an alias for the root xml tag
xstream.alias("Version", SparkVersion.class);
// Specify the main update url for JiveSoftware
this.mainUpdateURL = "http://www.igniterealtime.org/updater/updater";
sparkPluginInstalled = isSparkPluginInstalled(SparkManager.getConnection());
}
public SparkVersion newBuildAvailable() {
if (!sparkPluginInstalled && !Spark.disableUpdatesOnCustom()) {
// Handle Jivesoftware.org update
return isNewBuildAvailableFromJivesoftware();
}
else if (sparkPluginInstalled) {
try {
SparkVersion serverVersion = getLatestVersion(SparkManager.getConnection());
if (isGreater(serverVersion.getVersion(), JiveInfo.getVersion())) {
return serverVersion;
}
}
catch (XMPPException e) {
// Nothing to do
}
}
return null;
}
/**
* Returns true if there is a new build available for download.
*
* @return true if there is a new build available for download.
*/
public SparkVersion isNewBuildAvailableFromJivesoftware() {
PostMethod post = new PostMethod(mainUpdateURL);
if (Spark.isWindows()) {
post.addParameter("os", "windows");
}
else if (Spark.isMac()) {
post.addParameter("os", "mac");
}
else {
post.addParameter("os", "linux");
}
// Properties isBetaCheckingEnabled is now used to indicate if updates are allowed
// // Check to see if the beta should be included.
// LocalPreferences pref = SettingsManager.getLocalPreferences();
// boolean isBetaCheckingEnabled = pref.isBetaCheckingEnabled();
// if (isBetaCheckingEnabled) {
// post.addParameter("beta", "true");
// }
Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) {
try {
httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));
}
catch (NumberFormatException e) {
Log.error(e);
}
}
try {
int result = httpclient.executeMethod(post);
if (result != 200) {
return null;
}
String xml = post.getResponseBodyAsString();
// Server Version
SparkVersion serverVersion = (SparkVersion)xstream.fromXML(xml);
if (isGreater(serverVersion.getVersion(), JiveInfo.getVersion())) {
return serverVersion;
}
}
catch (IOException e) {
Log.error(e);
}
return null;
}
public void downloadUpdate(final File downloadedFile, final SparkVersion version) {
final java.util.Timer timer = new java.util.Timer();
// Prepare HTTP post
final GetMethod post = new GetMethod(version.getDownloadURL());
// Get HTTP client
Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443));
final HttpClient httpclient = new HttpClient();
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) {
try {
httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));
}
catch (NumberFormatException e) {
Log.error(e);
}
}
// Execute request
try {
int result = httpclient.executeMethod(post);
if (result != 200) {
return;
}
long length = post.getResponseContentLength();
int contentLength = (int)length;
bar = new JProgressBar(0, contentLength);
}
catch (IOException e) {
Log.error(e);
}
final JFrame frame = new JFrame(Res.getString("title.downloading.im.client"));
frame.setIconImage(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE).getImage());
titlePanel = new TitlePanel(Res.getString("title.upgrading.client"), Res.getString("message.version", version.getVersion()), SparkRes.getImageIcon(SparkRes.SEND_FILE_24x24), true);
final Thread thread = new Thread(new Runnable() {
public void run() {
try {
InputStream stream = post.getResponseBodyAsStream();
long size = post.getResponseContentLength();
ByteFormat formater = new ByteFormat();
sizeText = formater.format(size);
titlePanel.setDescription(Res.getString("message.version", version.getVersion()) + " \n" + Res.getString("message.file.size", sizeText));
downloadedFile.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(downloadedFile);
copy(stream, out);
out.close();
if (!cancel) {
downloadComplete = true;
promptForInstallation(downloadedFile, Res.getString("title.download.complete"), Res.getString("message.restart.spark"));
}
else {
out.close();
downloadedFile.delete();
}
UPDATING = false;
frame.dispose();
}
catch (Exception ex) {
// Nothing to do
}
finally {
timer.cancel();
// Release current connection to the connection pool once you are done
post.releaseConnection();
}
}
});
frame.getContentPane().setLayout(new GridBagLayout());
frame.getContentPane().add(titlePanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0));
frame.getContentPane().add(bar, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0));
JEditorPane pane = new JEditorPane();
boolean displayContentPane = version.getChangeLogURL() != null || version.getDisplayMessage() != null;
try {
pane.setEditable(false);
if (version.getChangeLogURL() != null) {
pane.setEditorKit(new HTMLEditorKit());
pane.setPage(version.getChangeLogURL());
}
else if (version.getDisplayMessage() != null) {
pane.setText(version.getDisplayMessage());
}
if (displayContentPane) {
frame.getContentPane().add(new JScrollPane(pane), new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
}
}
catch (IOException e) {
Log.error(e);
}
frame.getContentPane().setBackground(Color.WHITE);
frame.pack();
if (displayContentPane) {
frame.setSize(600, 400);
}
else {
frame.setSize(400, 100);
}
frame.setLocationRelativeTo(SparkManager.getMainWindow());
GraphicUtils.centerWindowOnScreen(frame);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent) {
thread.interrupt();
cancel = true;
UPDATING = false;
if (!downloadComplete) {
JOptionPane.showMessageDialog(SparkManager.getMainWindow(), Res.getString("message.updating.cancelled"), Res.getString("title.cancelled"), JOptionPane.ERROR_MESSAGE);
}
}
});
frame.setVisible(true);
thread.start();
timer.scheduleAtFixedRate(new TimerTask() {
int seconds = 1;
public void run() {
ByteFormat formatter = new ByteFormat();
long value = bar.getValue();
long average = value / seconds;
String text = formatter.format(average) + "/Sec";
String total = formatter.format(value);
titlePanel.setDescription(Res.getString("message.version", version.getVersion()) + " \n" + Res.getString("message.file.size", sizeText) + "\n" + Res.getString("message.transfer.rate") + ": " + text + "\n" + Res.getString("message.total.downloaded") + ": " + total);
seconds++;
}
}, 1000, 1000);
}
/**
* Common code for copy routines. By convention, the streams are
* closed in the same method in which they were opened. Thus,
* this method does not close the streams when the copying is done.
*
* @param in Source stream
* @param out Destination stream
*/
private void copy(final InputStream in, final OutputStream out) {
int read = 0;
try {
final byte[] buffer = new byte[4096];
while (!cancel) {
int bytesRead = in.read(buffer);
if (bytesRead < 0) {
break;
}
out.write(buffer, 0, bytesRead);
read += bytesRead;
bar.setValue(read);
}
}
catch (IOException e) {
Log.error(e);
}
}
/**
* Checks Spark Manager and/or Jive Software for the latest version of Spark.
*
* @param explicit true if the user explicitly asks for the latest version.
* @throws Exception if there is an error during check
*/
public void checkForUpdate(boolean explicit) throws Exception {
if (UPDATING) {
return;
}
UPDATING = true;
if (isLocalBuildAvailable()) {
return;
}
LocalPreferences localPreferences = SettingsManager.getLocalPreferences();
//defaults to 7, 0=disabled
int CheckForUpdates = localPreferences.getCheckForUpdates();
if (CheckForUpdates == 0) {
return;
}
Date lastChecked = localPreferences.getLastCheckForUpdates();
if (lastChecked == null) {
lastChecked = new Date();
// This is the first invocation of Communicator
localPreferences.setLastCheckForUpdates(lastChecked);
SettingsManager.saveSettings();
}
// Check to see if it has been a CheckForUpdates (default 7) days
Calendar calendar = Calendar.getInstance();
calendar.setTime(lastChecked);
calendar.add(Calendar.DATE, CheckForUpdates);
final Date lastCheckedPlusAPeriod = calendar.getTime();
boolean periodOrLonger = new Date().getTime() >= lastCheckedPlusAPeriod.getTime();
if (periodOrLonger || explicit || sparkPluginInstalled) {
if (!explicit && !localPreferences.isBetaCheckingEnabled())
{
return;
}
// Check version on server.
lastChecked = new Date();
localPreferences.setLastCheckForUpdates(lastChecked);
SettingsManager.saveSettings();
final SparkVersion serverVersion = newBuildAvailable();
if (serverVersion == null) {
UPDATING = false;
if (explicit) {
JOptionPane.showMessageDialog(SparkManager.getMainWindow(), Res.getString("message.no.updates"), Res.getString("title.no.updates"), JOptionPane.INFORMATION_MESSAGE);
}
return;
}
// Otherwise updates are available
String downloadURL = serverVersion.getDownloadURL();
String filename = downloadURL.substring(downloadURL.lastIndexOf("/") + 1);
if (filename.indexOf('=') != -1) {
filename = filename.substring(filename.indexOf('=') + 1);
}
// Set Download Directory
final File downloadDir = new File(Spark.getSparkUserHome(), "updates");
downloadDir.mkdirs();
// Set file to download.
final File fileToDownload = new File(downloadDir, filename);
if (fileToDownload.exists()) {
fileToDownload.delete();
}
ConfirmDialog confirm = new ConfirmDialog();
confirm.showConfirmDialog(SparkManager.getMainWindow(), Res.getString("title.new.version.available"),
Res.getString("message.new.spark.available", filename), Res.getString("yes"), Res.getString("no"),
null);
confirm.setDialogSize(400, 300);
confirm.setConfirmListener(new ConfirmListener() {
public void yesOption() {
SwingWorker worker = new SwingWorker() {
public Object construct() {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
Log.error(e);
}
return "ok";
}
public void finished() {
if (Spark.isWindows()) {
downloadUpdate(fileToDownload, serverVersion);
}
else {
// Launch browser to download page.
try {
if (sparkPluginInstalled) {
BrowserLauncher.openURL(serverVersion.getDownloadURL());
}
else {
BrowserLauncher.openURL("http://www.igniterealtime.org/downloads/index.jsp#spark");
}
}
catch (Exception e) {
Log.error(e);
}
UPDATING = false;
}
}
};
worker.start();
}
public void noOption() {
UPDATING = false;
}
});
}
else {
UPDATING = false;
}
}
/**
* Returns true if the first version number is greater than the second.
*
* @param firstVersion the first version number.
* @param secondVersion the second version number.
* @return returns true if the first version is greater than the second.
*/
public static boolean isGreater(String firstVersion, String secondVersion) {
int indexOne = firstVersion.indexOf("_");
if (indexOne != -1) {
firstVersion = firstVersion.substring(indexOne + 1);
}
int indexTwo = secondVersion.indexOf("_");
if (indexTwo != -1) {
secondVersion = secondVersion.substring(indexTwo + 1);
}
firstVersion = firstVersion.replaceAll(".online", "");
secondVersion = secondVersion.replace(".online", "");
boolean versionOneBetaOrAlpha = firstVersion.toLowerCase().contains("beta") || firstVersion.toLowerCase().contains("alpha");
boolean versionTwoBetaOrAlpha = secondVersion.toLowerCase().contains("beta") || secondVersion.toLowerCase().contains("alpha");
// Handle case where they are both betas / alphas
if ((versionOneBetaOrAlpha && versionTwoBetaOrAlpha) || (!versionOneBetaOrAlpha && !versionTwoBetaOrAlpha)) {
return firstVersion.compareTo(secondVersion) >= 1;
}
// Handle the case where version 1 is a beta or alpha
if (versionOneBetaOrAlpha) {
String versionOne = getVersion(firstVersion);
return versionOne.compareTo(secondVersion) >= 1;
}
else if (versionTwoBetaOrAlpha) {
String versionTwo = getVersion(secondVersion);
int result = firstVersion.compareTo(versionTwo);
return result >= 0;
}
return firstVersion.compareTo(secondVersion) >= 1;
}
public static String getVersion(String version) {
int lastIndexOf = version.lastIndexOf(".");
if (lastIndexOf != -1) {
return version.substring(0, lastIndexOf);
}
return version;
}
/**
* Returns the latest version of Spark available via Spark Manager or Jive Software.
*
* @param connection the XMPPConnection to use.
* @return the information for about the latest Spark Client.
* @throws XMPPException If unable to retrieve latest version.
*/
public static SparkVersion getLatestVersion(XMPPConnection connection) throws XMPPException {
SparkVersion request = new SparkVersion();
request.setType(IQ.Type.GET);
request.setTo("updater." + connection.getServiceName());
PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID()));
connection.sendPacket(request);
SparkVersion response = (SparkVersion)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Cancel the collector.
collector.cancel();
if (response == null) {
throw new XMPPException("No response from server.");
}
if (response.getError() != null) {
throw new XMPPException(response.getError());
}
return response;
}
/**
* Does a service discvery on the server to see if a Spark Manager
* is enabled.
*
* @param con the XMPPConnection to use.
* @return true if Spark Manager is available.
*/
public static boolean isSparkPluginInstalled(XMPPConnection con) {
if (!con.isConnected()) {
return false;
}
try {
DiscoverItems items = SparkManager.getSessionManager().getDiscoveredItems();
Iterator<DiscoverItems.Item> iter = items.getItems();
while (iter.hasNext()) {
DiscoverItems.Item item = (DiscoverItems.Item)iter.next();
if ("Spark Updater".equals(item.getName())) {
return true;
}
}
}
catch (Exception e) {
Log.error(e);
}
return false;
}
/**
* Prompts the user to install the latest Spark.
*
* @param downloadedFile the location of the latest downloaded client.
* @param title the title
* @param message the message
*/
private void promptForInstallation(final File downloadedFile, String title, String message) {
ConfirmDialog confirm = new ConfirmDialog();
confirm.showConfirmDialog(SparkManager.getMainWindow(), title,
message, Res.getString("yes"), Res.getString("no"),
null);
confirm.setConfirmListener(new ConfirmListener() {
public void yesOption() {
try {
if (Spark.isWindows()) {
Runtime.getRuntime().exec(downloadedFile.getAbsolutePath());
}
else if (Spark.isMac()) {
Runtime.getRuntime().exec("open " + downloadedFile.getCanonicalPath());
}
}
catch (IOException e) {
Log.error(e);
}
SparkManager.getMainWindow().shutdown();
}
public void noOption() {
}
});
}
/**
* Checks to see if a new version of Spark has already been downloaded by not installed.
*
* @return true if a newer version exists.
*/
private boolean isLocalBuildAvailable() {
// Check the bin directory for previous downloads. If there is a
// newer version of Spark, ask if they wish to install.
if (Spark.isWindows()) {
File binDirectory = Spark.getBinDirectory();
File[] files = binDirectory.listFiles();
if (files != null) {
int no = files.length;
for (int i = 0; i < no; i++) {
File file = files[i];
String fileName = file.getName();
if (fileName.endsWith(".exe")) {
int index = fileName.indexOf("_");
// Add version number
String versionNumber = fileName.substring(index + 1);
int indexOfPeriod = versionNumber.indexOf(".");
versionNumber = versionNumber.substring(0, indexOfPeriod);
versionNumber = versionNumber.replaceAll("_online", "");
versionNumber = versionNumber.replaceAll("_", ".");
boolean isGreater = versionNumber.compareTo(JiveInfo.getVersion()) >= 1;
if (isGreater) {
// Prompt
promptForInstallation(file, Res.getString("title.new.client.available"), Res.getString("message.restart.spark.to.install"));
return true;
}
else {
file.delete();
}
}
}
}
}
return false;
}
}