/* * Created on 7 mai 2004 * Created by Olivier Chalouhi * * Copyright (C) 2004, 2005, 2006 Aelitis SAS, All rights Reserved * * This program 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 2 of the License. * * This program 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 ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * AELITIS, SAS au capital de 46,603.30 euros, * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. */ package org.gudy.azureus2.ui.swt.update; import java.io.File; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Shell; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.ui.swt.Utils; import org.gudy.azureus2.ui.swt.components.StringListChooser; import org.gudy.azureus2.ui.swt.progress.IProgressReport; import org.gudy.azureus2.ui.swt.progress.IProgressReportConstants; import org.gudy.azureus2.ui.swt.progress.IProgressReporter; import org.gudy.azureus2.ui.swt.progress.IProgressReporterListener; import org.gudy.azureus2.ui.swt.progress.ProgressReportingManager; import org.gudy.azureus2.update.CoreUpdateChecker; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.versioncheck.VersionCheckClient; import com.aelitis.azureus.ui.*; import com.aelitis.azureus.ui.swt.UIFunctionsManagerSWT; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.update.*; import org.gudy.azureus2.plugins.utils.DelayedTask; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader; import org.gudy.azureus2.pluginsimpl.local.PluginInitializer; import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl; /** * @author Olivier Chalouhi * */ public class UpdateMonitor implements UpdateCheckInstanceListener { private static final LogIDs LOGID = LogIDs.GUI; public static final long AUTO_UPDATE_CHECK_PERIOD = 23 * 60 * 60 * 1000; // 23 hours public static final long AUTO_UPDATE_CHECK_PERIOD_BETA = 4 * 60 * 60 * 1000; // 4 hours private static final String MSG_PREFIX = "UpdateMonitor.messagebox."; private static UpdateMonitor singleton; private static AEMonitor class_mon = new AEMonitor("UpdateMonitor:class"); public static UpdateMonitor getSingleton(AzureusCore core) { try { class_mon.enter(); if (singleton == null) { singleton = new UpdateMonitor(core); } return (singleton); } finally { class_mon.exit(); } } private AzureusCore azCore; private UpdateWindow current_update_window; private UpdateCheckInstance current_update_instance; private long last_recheck_time; protected UpdateMonitor(AzureusCore _azureus_core) { azCore = _azureus_core; PluginInterface defPI = PluginInitializer.getDefaultInterface(); UpdateManager um = defPI.getUpdateManager(); um.addListener(new UpdateManagerListener() { public void checkInstanceCreated(UpdateCheckInstance instance) { instance.addListener(UpdateMonitor.this); if ( !instance.isLowNoise()){ new updateStatusChanger(instance); } } }); um.addVerificationListener(new UpdateManagerVerificationListener() { public boolean acceptUnVerifiedUpdate(final Update update) { UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { String title = MessageText.getString(MSG_PREFIX + "accept.unverified.title"); String text = MessageText.getString(MSG_PREFIX + "accept.unverified.text", new String[] { update.getName() }); UIFunctionsUserPrompter prompter = uiFunctions.getUserPrompter(title, text, new String[] { MessageText.getString("Button.yes"), MessageText.getString("Button.no") }, 1); prompter.setRemember(MSG_PREFIX + "accept.unverified", false, MessageText.getString("MessageBoxWindow.nomoreprompting")); prompter.setAutoCloseInMS(0); prompter.open(null); return prompter.waitUntilClosed() == 0; } return false; } public void verificationFailed(final Update update, final Throwable cause) { final String cause_str = Debug.getNestedExceptionMessage(cause); UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { String title = MessageText.getString(MSG_PREFIX + "verification.failed.title"); String text = MessageText.getString(MSG_PREFIX + "verification.failed.text", new String[] { update.getName(), cause_str }); uiFunctions.promptUser(title, text, new String[] { MessageText.getString("Button.ok") }, 0, null, null, false, 0, null); } } }); SimpleTimer.addPeriodicEvent("UpdateMon:autocheck", COConfigurationManager.getBooleanParameter( "Beta Programme Enabled" )?AUTO_UPDATE_CHECK_PERIOD_BETA:AUTO_UPDATE_CHECK_PERIOD, new TimerEventPerformer() { public void perform(TimerEvent ev) { performAutoCheck(false); } }); DelayedTask delayed_task = UtilitiesImpl.addDelayedTask( "Update Check", new Runnable() { public void run() { // check for non-writeable app dir on non-vista platforms (vista we've got a chance of // elevating perms when updating) and warn user. Particularly useful on OSX when // users haven't installed properly if ( !( Constants.isWindowsVistaOrHigher || SystemProperties.isJavaWebStartInstance())){ String app_str = SystemProperties.getApplicationPath(); if ( !new File(app_str).canWrite()){ final UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if ( uiFunctions != null ){ if ( app_str.endsWith( File.separator )){ app_str = app_str.substring(0, app_str.length()-1); } final String f_app_str = app_str; Utils.execSWTThread( new Runnable() { public void run() { UIFunctionsUserPrompter prompt = uiFunctions.getUserPrompter( MessageText.getString("updater.cant.write.to.app.title"), MessageText.getString("updater.cant.write.to.app.details", new String[]{f_app_str}), new String[]{ MessageText.getString( "Button.ok" )}, 0 ); //prompt.setHtml( "http://a.b.c/" ); prompt.setIconResource( "warning" ); prompt.setRemember( "UpdateMonitor.can.not.write.to.app.dir.2", false, MessageText.getString( "MessageBoxWindow.nomoreprompting" )); prompt.open(null); } }, true ); } } } performAutoCheck(true); } }); delayed_task.queue(); } protected class updateStatusChanger implements IProgressReportConstants { UpdateCheckInstance instance; int check_num = 0; /* * Creates a ProgressReporter for the update process */ IProgressReporter updateReporter = ProgressReportingManager.getInstance().addReporter( MessageText.getString("UpdateWindow.title")); protected updateStatusChanger(UpdateCheckInstance _instance) { instance = _instance; /* * Init reporter and allow cancel */ updateReporter.setReporterType("reporterType_updater"); updateReporter.setCancelAllowed(true); updateReporter.setTitle(MessageText.getString("updater.progress.window.title")); updateReporter.appendDetailMessage(format(instance, "added")); String name = instance.getName(); if (MessageText.keyExists(name)) { updateReporter.setMessage(MessageText.getString(name)); } else { updateReporter.setMessage(name); } updateReporter.setMinimum(0); updateReporter.setMaximum(instance.getCheckers().length); updateReporter.setSelection(check_num, null); /* * Add a listener to the reporter for a cancel event and cancel the update * check instance if the event is detected */ updateReporter.addListener(new IProgressReporterListener() { public int report(IProgressReport progressReport) { if (progressReport.getReportType() == REPORT_TYPE_DONE || progressReport.getReportType() == REPORT_TYPE_ERROR) { return RETVAL_OK_TO_DISPOSE; } if (progressReport.getReportType() == REPORT_TYPE_CANCEL) { if (null != instance) { instance.cancel(); } return RETVAL_OK_TO_DISPOSE; } return RETVAL_OK; } }); /* * Add listener to the running state of the update check instance and forward * to the reporter when they arrive */ instance.addListener(new UpdateCheckInstanceListener() { public void cancelled(UpdateCheckInstance instance) { updateReporter.appendDetailMessage(format(instance, MessageText.getString("Progress.reporting.status.canceled"))); updateReporter.cancel(); } public void complete(UpdateCheckInstance instance) { updateReporter.appendDetailMessage(format(instance, MessageText.getString("Progress.reporting.status.finished"))); updateReporter.setDone(); } }); UpdateChecker[] checkers = instance.getCheckers(); for (int i = 0; i < checkers.length; i++) { final UpdateChecker checker = checkers[i]; /* * Add update check listener to get running state */ checker.addListener(new UpdateCheckerListener() { public void cancelled(UpdateChecker checker) { // we don't count a cancellation as progress step updateReporter.appendDetailMessage(format(checker, MessageText.getString("Progress.reporting.status.canceled"))); } public void completed(UpdateChecker checker) { updateReporter.appendDetailMessage(format(checker, MessageText.getString("Progress.reporting.status.finished"))); updateReporter.setSelection(++check_num, null); } public void failed(UpdateChecker checker) { updateReporter.appendDetailMessage(format(checker, MessageText.getString("Progress.reporting.default.error"))); updateReporter.setSelection(++check_num, null); // notify user of a failed update, use default error message updateReporter.setErrorMessage(null); } }); /* * Add a listener to get the detail messages */ checker.addProgressListener(new UpdateProgressListener() { public void reportProgress(String str) { updateReporter.appendDetailMessage(format(checker, " " + str)); } }); } } } // ============================================================ // Convenience methods for formatting the detail messages for // the update process // ============================================================ private String format(UpdateCheckInstance instance, String str) { String name = instance.getName(); if (MessageText.keyExists(name)) { name = MessageText.getString(name); } return name + " - " + str; } private String format(UpdateChecker checker, String str) { return " " + checker.getComponent().getName() + " - " + str; } protected void requestRecheck() { if (Logger.isEnabled()){ Logger.log(new LogEvent(LOGID, "UpdateMonitor: recheck requested" )); } performCheck( false, true, true, null ); } protected void performAutoCheck(final boolean start_of_day) { boolean check_at_start = false; boolean check_periodic = false; boolean bOldSWT = SWT.getVersion() < 3139; // no update checks for java web start if (!SystemProperties.isJavaWebStartInstance()) { // force check when SWT is really old check_at_start = COConfigurationManager.getBooleanParameter("update.start") || bOldSWT; check_periodic = COConfigurationManager.getBooleanParameter("update.periodic"); } // periodic -> check at start as well check_at_start = check_at_start || check_periodic; if ((check_at_start && start_of_day) || (check_periodic && !start_of_day)) { performCheck(bOldSWT, true, false, null ); // this will implicitly do usage stats } else { new DelayedEvent("UpdateMon:wait2", 5000, new AERunnable() { public void runSupport() { if (start_of_day) { UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { uiFunctions.setStatusText(""); } } CoreUpdateChecker.doUsageStats(); } }); } } public void performCheck( final boolean bForce, final boolean automatic, final boolean isRecheck, final UpdateCheckInstanceListener l ) { long now = SystemTime.getCurrentTime(); if ( isRecheck ){ if ( last_recheck_time > now || now - last_recheck_time < 23*60*60*1000 ){ if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "skipping recheck as consecutive recheck too soon")); return; } last_recheck_time = now; }else{ last_recheck_time = 0; } if (SystemProperties.isJavaWebStartInstance()) { // just in case we get here somehome! if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "skipping update check as java web start")); return; } // kill any existing update window if (current_update_window != null && !current_update_window.isDisposed()) { current_update_window.dispose(); } if (current_update_instance != null) { current_update_instance.cancel(); } if ( bForce ){ VersionCheckClient.getSingleton().clearCache(); } UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { // XXX What kind of format is this!? uiFunctions.setStatusText("MainWindow.status.checking ..."); } // take this off this GUI thread in case it blocks for a while AEThread2 t = new AEThread2("UpdateMonitor:kickoff", true) { public void run() { UpdateManager um = PluginInitializer.getDefaultInterface().getUpdateManager(); current_update_instance = um.createUpdateCheckInstance(bForce ? UpdateCheckInstance.UCI_INSTALL : UpdateCheckInstance.UCI_UPDATE, "update.instance.update"); if (!automatic) { current_update_instance.setAutomatic(false); } if (l != null) { current_update_instance.addListener(l); } current_update_instance.start(); } }; t.start(); } public void complete( final UpdateCheckInstance instance) { if ( instance.isLowNoise()){ handleLowNoise( instance ); return; } boolean hasDownloads = false; Update[] us = instance.getUpdates(); // updates with zero-length downloaders exist for admin purposes // and shoudn't cause the window to be shown if only they exist for (int i = 0; i < us.length; i++) { if (us[i].getDownloaders().length > 0) { hasDownloads = true; break; } } try{ int ui = (Integer)instance.getProperty( UpdateCheckInstance.PT_UI_STYLE ); if ( ui == UpdateCheckInstance.PT_UI_STYLE_SIMPLE ){ new SimpleInstallUI( this, instance ); return; }else if ( ui == UpdateCheckInstance.PT_UI_STYLE_NONE ){ new SilentInstallUI( this, instance ); return; } }catch( Throwable e ){ Debug.printStackTrace(e); } // we can get here for either update actions (triggered above) or for plugin // install actions (triggered by the plugin installer) boolean update_action = instance.getType() == UpdateCheckInstance.UCI_UPDATE; UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { uiFunctions.setStatusText(""); } // this controls whether or not the update window is displayed // note that we just don't show the window if this is set, we still do the // update check (as amongst other things we want ot know the latest // version of the core anyway if (hasDownloads) { // don't show another update if one's already there! UpdateWindow this_window = null; boolean autoDownload = COConfigurationManager.getBooleanParameter("update.autodownload"); if (update_action) { if (!autoDownload && (current_update_window == null || current_update_window.isDisposed())) { this_window = current_update_window = new UpdateWindow( this, azCore,instance); } } else { // always show an installer window this_window = new UpdateWindow( this, azCore, instance); } if (this_window != null) { for (int i = 0; i < us.length; i++) { if (us[i].getDownloaders().length > 0) { this_window.addUpdate(us[i]); } } this_window.updateAdditionComplete(); } else { if (autoDownload) { new UpdateAutoDownloader(us, new UpdateAutoDownloader.cbCompletion() { public void allUpdatesComplete( boolean requiresRestart, boolean bHadMandatoryUpdates) { Boolean b = (Boolean)instance.getProperty( UpdateCheckInstance.PT_CLOSE_OR_RESTART_ALREADY_IN_PROGRESS ); if ( b != null && b ){ return; } if (requiresRestart) { handleRestart(); }else if ( bHadMandatoryUpdates ){ // no restart and mandatory -> rescan for optional updates now requestRecheck(); } } }); } else { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "UpdateMonitor: user dialog already " + "in progress, updates skipped")); } } } else { if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "UpdateMonitor: check instance " + "resulted in no user-actionable updates")); } } public void cancelled(UpdateCheckInstance instance) { UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if (uiFunctions != null) { uiFunctions.setStatusText(""); } } protected void handleRestart() { final UIFunctions uiFunctions = UIFunctionsManager.getUIFunctions(); if ( uiFunctions != null ){ uiFunctions.performAction( UIFunctions.ACTION_UPDATE_RESTART_REQUEST, Constants.isWindows7OrHigher, // no timer for in 7 as they always get an elevation prompt so we don't want to shutdown and then leave\ // Vuze down pending user authorisation of the update new UIFunctions.actionListener() { public void actionComplete( Object result ) { if ((Boolean)result){ uiFunctions.dispose(true, false); } } }); } } protected void addDecisionHandler( UpdateCheckInstance instance ) { instance.addDecisionListener( new UpdateManagerDecisionListener() { public Object decide( Update update, int decision_type, String decision_name, String decision_description, Object decision_data ) { if ( decision_type == UpdateManagerDecisionListener.DT_STRING_ARRAY_TO_STRING ){ String[] options = (String[])decision_data; Shell shell = UIFunctionsManagerSWT.getUIFunctionsSWT().getMainShell(); if ( shell == null ){ Debug.out( "Shell doesn't exist" ); return( null ); } StringListChooser chooser = new StringListChooser( shell ); chooser.setTitle( decision_name ); chooser.setText( decision_description ); for (int i=0;i<options.length;i++){ chooser.addOption( options[i] ); } String result = chooser.open(); return( result ); } return( null ); } }); } protected void handleLowNoise( UpdateCheckInstance instance ) { addDecisionHandler( instance ); Update[] updates = instance.getUpdates(); try{ for (int i=0;i<updates.length;i++){ ResourceDownloader[] downloaders = updates[i].getDownloaders(); for (int j=0;j<downloaders.length;j++){ downloaders[j].download(); } } boolean restart_required = false; for (int i=0;i<updates.length;i++){ if ( updates[i].getRestartRequired() == Update.RESTART_REQUIRED_YES ){ restart_required = true; } } if ( restart_required ){ handleRestart(); } }catch( Throwable e ){ // TODO: e.printStackTrace(); } } }