upgrade_detector.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
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/file_version_info.h"
11#include "base/scoped_ptr.h"
12#include "base/time.h"
13#include "base/task.h"
14#include "base/utf_string_conversions.h"
15#include "base/version.h"
16#include "chrome/browser/chrome_thread.h"
17#include "chrome/browser/pref_service.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/common/chrome_version_info.h"
20#include "chrome/common/notification_service.h"
21#include "chrome/common/notification_type.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/installer/util/browser_distribution.h"
24
25#if defined(OS_WIN)
26#include "chrome/installer/util/install_util.h"
27#elif defined(OS_MACOSX)
28#include "chrome/browser/cocoa/keystone_glue.h"
29#elif defined(OS_POSIX)
30#include "base/process_util.h"
31#include "chrome/installer/util/version.h"
32#endif
33
34// TODO(finnur): For the stable channel we want to check daily and notify
35// the user if more than 2 weeks have passed since the upgrade happened
36// (without a reboot). For the dev channel however, I want quicker feedback
37// on how the feature works so I'm checking every hour and notifying the
38// user immediately.
39
40namespace {
41
42// How often to check for an upgrade.
43const int kCheckForUpgradeEveryMs = 60 * 60 * 1000;  // 1 hour.
44
45// How long to wait before notifying the user about the upgrade.
46const int kNotifyUserAfterMs = 0;
47
48// The thread to run the upgrade detection code on. We use FILE for Linux
49// because we don't want to block the UI thread while launching a background
50// process and reading its output; on the Mac, checking for an upgrade
51// requires reading a file.
52const ChromeThread::ID kDetectUpgradeTaskID =
53#if defined(OS_POSIX)
54    ChromeThread::FILE;
55#else
56    ChromeThread::UI;
57#endif
58
59// This task checks the currently running version of Chrome against the
60// installed version. If the installed version is newer, it runs the passed
61// callback task. Otherwise it just deletes the task.
62class DetectUpgradeTask : public Task {
63 public:
64  explicit DetectUpgradeTask(Task* upgrade_detected_task)
65      : upgrade_detected_task_(upgrade_detected_task) {
66  }
67
68  virtual ~DetectUpgradeTask() {
69    if (upgrade_detected_task_) {
70      // This has to get deleted on the same thread it was created.
71      ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
72                             new DeleteTask<Task>(upgrade_detected_task_));
73    }
74  }
75
76  virtual void Run() {
77    DCHECK(ChromeThread::CurrentlyOn(kDetectUpgradeTaskID));
78
79    using installer::Version;
80    scoped_ptr<Version> installed_version;
81
82#if defined(OS_WIN)
83    // Get the version of the currently *installed* instance of Chrome,
84    // which might be newer than the *running* instance if we have been
85    // upgraded in the background.
86    installed_version.reset(InstallUtil::GetChromeVersion(false));
87    if (!installed_version.get()) {
88      // User level Chrome is not installed, check system level.
89      installed_version.reset(InstallUtil::GetChromeVersion(true));
90    }
91#elif defined(OS_MACOSX)
92    installed_version.reset(
93        Version::GetVersionFromString(
94            keystone_glue::CurrentlyInstalledVersion()));
95#elif defined(OS_POSIX)
96    // POSIX but not Mac OS X: Linux, etc.
97    CommandLine command_line(*CommandLine::ForCurrentProcess());
98    command_line.AppendSwitch(switches::kProductVersion);
99    std::string reply;
100    if (!base::GetAppOutput(command_line, &reply)) {
101      DLOG(ERROR) << "Failed to get current file version";
102      return;
103    }
104
105    installed_version.reset(Version::GetVersionFromString(ASCIIToUTF16(reply)));
106#endif
107
108    // Get the version of the currently *running* instance of Chrome.
109    scoped_ptr<FileVersionInfo> version(chrome::GetChromeVersionInfo());
110    if (version.get() == NULL) {
111      NOTREACHED() << "Failed to get current file version";
112      return;
113    }
114
115    scoped_ptr<Version> running_version(
116        Version::GetVersionFromString(WideToUTF16(version->file_version())));
117    if (running_version.get() == NULL) {
118      NOTREACHED() << "Failed to parse version info";
119      return;
120    }
121
122    // |installed_version| may be NULL when the user downgrades on Linux (by
123    // switching from dev to beta channel, for example). The user needs a
124    // restart in this case as well. See http://crbug.com/46547
125    if (!installed_version.get() ||
126        installed_version->IsHigherThan(running_version.get())) {
127      ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
128                             upgrade_detected_task_);
129      upgrade_detected_task_ = NULL;
130    }
131  }
132
133 private:
134  Task* upgrade_detected_task_;
135};
136
137}  // namespace
138
139// static
140void UpgradeDetector::RegisterPrefs(PrefService* prefs) {
141  prefs->RegisterBooleanPref(prefs::kRestartLastSessionOnShutdown, false);
142}
143
144UpgradeDetector::UpgradeDetector()
145    : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
146      notify_upgrade_(false) {
147  // Windows: only enable upgrade notifications for official builds.
148  // Mac: only enable them if the updater (Keystone) is present.
149  // Linux (and other POSIX): always enable regardless of branding.
150#if (defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)) || defined(OS_POSIX)
151#if defined(OS_MACOSX)
152  if (keystone_glue::KeystoneEnabled())
153#endif
154  {
155    detect_upgrade_timer_.Start(
156        base::TimeDelta::FromMilliseconds(kCheckForUpgradeEveryMs),
157        this, &UpgradeDetector::CheckForUpgrade);
158  }
159#endif
160}
161
162UpgradeDetector::~UpgradeDetector() {
163}
164
165void UpgradeDetector::CheckForUpgrade() {
166  method_factory_.RevokeAll();
167  Task* callback_task =
168      method_factory_.NewRunnableMethod(&UpgradeDetector::UpgradeDetected);
169  ChromeThread::PostTask(kDetectUpgradeTaskID, FROM_HERE,
170                         new DetectUpgradeTask(callback_task));
171}
172
173void UpgradeDetector::UpgradeDetected() {
174  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
175
176  // Stop the recurring timer (that is checking for changes).
177  detect_upgrade_timer_.Stop();
178
179  NotificationService::current()->Notify(
180      NotificationType::UPGRADE_DETECTED,
181      Source<UpgradeDetector>(this),
182      NotificationService::NoDetails());
183
184  // Start the OneShot timer for notifying the user after a certain period.
185  upgrade_notification_timer_.Start(
186      base::TimeDelta::FromMilliseconds(kNotifyUserAfterMs),
187      this, &UpgradeDetector::NotifyOnUpgrade);
188}
189
190void UpgradeDetector::NotifyOnUpgrade() {
191  notify_upgrade_ = true;
192
193  NotificationService::current()->Notify(
194      NotificationType::UPGRADE_RECOMMENDED,
195      Source<UpgradeDetector>(this),
196      NotificationService::NoDetails());
197}
198