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