upgrade_detector.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/upgrade_detector.h" 6 7#include <string> 8 9#include "base/command_line.h" 10#include "base/singleton.h" 11#include "base/scoped_ptr.h" 12#include "base/time.h" 13#include "base/task.h" 14#include "base/string_number_conversions.h" 15#include "base/string_util.h" 16#include "base/utf_string_conversions.h" 17#include "chrome/browser/browser_thread.h" 18#include "chrome/browser/platform_util.h" 19#include "chrome/browser/prefs/pref_service.h" 20#include "chrome/common/chrome_switches.h" 21#include "chrome/common/chrome_version_info.h" 22#include "chrome/common/notification_service.h" 23#include "chrome/common/notification_type.h" 24#include "chrome/common/pref_names.h" 25#include "chrome/installer/util/browser_distribution.h" 26 27#if defined(OS_WIN) 28#include "chrome/installer/util/install_util.h" 29#elif defined(OS_MACOSX) 30#include "chrome/browser/ui/cocoa/keystone_glue.h" 31#elif defined(OS_POSIX) 32#include "base/process_util.h" 33#include "base/version.h" 34#endif 35 36namespace { 37 38// How often to check for an upgrade. 39int GetCheckForUpgradeEveryMs() { 40 // Check for a value passed via the command line. 41 int interval_ms; 42 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 43 std::string interval = 44 cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec); 45 if (!interval.empty() && base::StringToInt(interval, &interval_ms)) 46 return interval_ms * 1000; // Command line value is in seconds. 47 48 // Otherwise check once an hour for dev channel and once a day for all other 49 // channels/builds. 50 const std::string channel = platform_util::GetVersionStringModifier(); 51 int hours; 52 if (channel == "dev") 53 hours = 1; 54 else 55 hours = 24; 56 57 return hours * 60 * 60 * 1000; 58} 59 60// How long to wait before notifying the user about the upgrade. 61const int kNotifyUserAfterMs = 0; 62 63// This task checks the currently running version of Chrome against the 64// installed version. If the installed version is newer, it runs the passed 65// callback task. Otherwise it just deletes the task. 66class DetectUpgradeTask : public Task { 67 public: 68 explicit DetectUpgradeTask(Task* upgrade_detected_task) 69 : upgrade_detected_task_(upgrade_detected_task) { 70 } 71 72 virtual ~DetectUpgradeTask() { 73 if (upgrade_detected_task_) { 74 // This has to get deleted on the same thread it was created. 75 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 76 new DeleteTask<Task>(upgrade_detected_task_)); 77 } 78 } 79 80 virtual void Run() { 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 82 83 scoped_ptr<Version> installed_version; 84 85#if defined(OS_WIN) 86 // Get the version of the currently *installed* instance of Chrome, 87 // which might be newer than the *running* instance if we have been 88 // upgraded in the background. 89 // TODO(tommi): Check if using the default distribution is always the right 90 // thing to do. 91 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 92 installed_version.reset(InstallUtil::GetChromeVersion(dist, false)); 93 if (!installed_version.get()) { 94 // User level Chrome is not installed, check system level. 95 installed_version.reset(InstallUtil::GetChromeVersion(dist, true)); 96 } 97#elif defined(OS_MACOSX) 98 installed_version.reset( 99 Version::GetVersionFromString(UTF16ToASCII( 100 keystone_glue::CurrentlyInstalledVersion()))); 101#elif defined(OS_POSIX) 102 // POSIX but not Mac OS X: Linux, etc. 103 CommandLine command_line(*CommandLine::ForCurrentProcess()); 104 command_line.AppendSwitch(switches::kProductVersion); 105 std::string reply; 106 if (!base::GetAppOutput(command_line, &reply)) { 107 DLOG(ERROR) << "Failed to get current file version"; 108 return; 109 } 110 111 installed_version.reset(Version::GetVersionFromString(reply)); 112#endif 113 114 // Get the version of the currently *running* instance of Chrome. 115 chrome::VersionInfo version_info; 116 if (!version_info.is_valid()) { 117 NOTREACHED() << "Failed to get current file version"; 118 return; 119 } 120 scoped_ptr<Version> running_version( 121 Version::GetVersionFromString(version_info.Version())); 122 if (running_version.get() == NULL) { 123 NOTREACHED() << "Failed to parse version info"; 124 return; 125 } 126 127 // |installed_version| may be NULL when the user downgrades on Linux (by 128 // switching from dev to beta channel, for example). The user needs a 129 // restart in this case as well. See http://crbug.com/46547 130 if (!installed_version.get() || 131 (installed_version->CompareTo(*running_version) > 0)) { 132 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 133 upgrade_detected_task_); 134 upgrade_detected_task_ = NULL; 135 } 136 } 137 138 private: 139 Task* upgrade_detected_task_; 140}; 141 142} // namespace 143 144// static 145void UpgradeDetector::RegisterPrefs(PrefService* prefs) { 146 prefs->RegisterBooleanPref(prefs::kRestartLastSessionOnShutdown, false); 147} 148 149UpgradeDetector::UpgradeDetector() 150 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), 151 notify_upgrade_(false) { 152 // Windows: only enable upgrade notifications for official builds. 153 // Mac: only enable them if the updater (Keystone) is present. 154 // Linux (and other POSIX): always enable regardless of branding. 155#if (defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)) || defined(OS_POSIX) 156#if defined(OS_MACOSX) 157 if (keystone_glue::KeystoneEnabled()) 158#endif 159 { 160 detect_upgrade_timer_.Start( 161 base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()), 162 this, &UpgradeDetector::CheckForUpgrade); 163 } 164#endif 165} 166 167UpgradeDetector::~UpgradeDetector() { 168} 169 170// static 171UpgradeDetector* UpgradeDetector::GetInstance() { 172 return Singleton<UpgradeDetector>::get(); 173} 174 175void UpgradeDetector::CheckForUpgrade() { 176 method_factory_.RevokeAll(); 177 Task* callback_task = 178 method_factory_.NewRunnableMethod(&UpgradeDetector::UpgradeDetected); 179 // We use FILE as the thread to run the upgrade detection code on all 180 // platforms. For Linux, this is because we don't want to block the UI thread 181 // while launching a background process and reading its output; on the Mac and 182 // on Windows checking for an upgrade requires reading a file. 183 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 184 new DetectUpgradeTask(callback_task)); 185} 186 187void UpgradeDetector::UpgradeDetected() { 188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 189 190 // Stop the recurring timer (that is checking for changes). 191 detect_upgrade_timer_.Stop(); 192 193 NotificationService::current()->Notify( 194 NotificationType::UPGRADE_DETECTED, 195 Source<UpgradeDetector>(this), 196 NotificationService::NoDetails()); 197 198 // Start the OneShot timer for notifying the user after a certain period. 199 upgrade_notification_timer_.Start( 200 base::TimeDelta::FromMilliseconds(kNotifyUserAfterMs), 201 this, &UpgradeDetector::NotifyOnUpgrade); 202} 203 204void UpgradeDetector::NotifyOnUpgrade() { 205 notify_upgrade_ = true; 206 207 NotificationService::current()->Notify( 208 NotificationType::UPGRADE_RECOMMENDED, 209 Source<UpgradeDetector>(this), 210 NotificationService::NoDetails()); 211} 212