1// Copyright (c) 2013 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/chromeos/extensions/install_limiter.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/file_util.h"
11#include "base/threading/sequenced_worker_pool.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/chromeos/extensions/install_limiter_factory.h"
14#include "content/public/browser/browser_thread.h"
15#include "content/public/browser/notification_details.h"
16#include "content/public/browser/notification_source.h"
17
18using content::BrowserThread;
19
20namespace {
21
22// Gets the file size of |file| and stores it in |size| on the blocking pool.
23void GetFileSizeOnBlockingPool(const base::FilePath& file, int64* size) {
24  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
25
26  // Get file size. In case of error, sets 0 as file size to let the installer
27  // run and fail.
28  if (!file_util::GetFileSize(file, size))
29    *size = 0;
30}
31
32}  // namespace
33
34namespace extensions {
35
36////////////////////////////////////////////////////////////////////////////////
37// InstallLimiter::DeferredInstall
38
39InstallLimiter::DeferredInstall::DeferredInstall(
40    const scoped_refptr<CrxInstaller>& installer,
41    const base::FilePath& path)
42    : installer(installer),
43      path(path) {
44}
45
46InstallLimiter::DeferredInstall::~DeferredInstall() {
47}
48
49////////////////////////////////////////////////////////////////////////////////
50// InstallLimiter
51
52InstallLimiter* InstallLimiter::Get(Profile* profile) {
53  return InstallLimiterFactory::GetForProfile(profile);
54}
55
56InstallLimiter::InstallLimiter() : disabled_for_test_(false) {
57}
58
59InstallLimiter::~InstallLimiter() {
60}
61
62void InstallLimiter::DisableForTest() {
63  disabled_for_test_ = true;
64}
65
66void InstallLimiter::Add(const scoped_refptr<CrxInstaller>& installer,
67                         const base::FilePath& path) {
68  // No deferred installs when disabled for test.
69  if (disabled_for_test_) {
70    installer->InstallCrx(path);
71    return;
72  }
73
74  int64* size = new int64(0);  // Owned by reply callback below.
75  BrowserThread::PostBlockingPoolTaskAndReply(
76      FROM_HERE,
77      base::Bind(&GetFileSizeOnBlockingPool, path, size),
78      base::Bind(&InstallLimiter::AddWithSize, AsWeakPtr(),
79                 installer, path, base::Owned(size)));
80}
81
82void InstallLimiter::AddWithSize(
83    const scoped_refptr<CrxInstaller>& installer,
84    const base::FilePath& path,
85    int64* size) {
86  const int64 kBigAppSizeThreshold = 1048576;  // 1MB
87
88  if (*size <= kBigAppSizeThreshold) {
89    RunInstall(installer, path);
90
91    // Stop wait timer and let install notification drive deferred installs.
92    wait_timer_.Stop();
93    return;
94  }
95
96  deferred_installs_.push(DeferredInstall(installer, path));
97
98  // When there are no running installs, wait a bit before running deferred
99  // installs to allow small app install to take precedence, especially when a
100  // big app is the first one in the list.
101  if (running_installers_.empty() && !wait_timer_.IsRunning()) {
102    const int kMaxWaitTimeInMs = 5000;  // 5 seconds.
103    wait_timer_.Start(
104        FROM_HERE,
105        base::TimeDelta::FromMilliseconds(kMaxWaitTimeInMs),
106        this, &InstallLimiter::CheckAndRunDeferrredInstalls);
107  }
108}
109
110void InstallLimiter::CheckAndRunDeferrredInstalls() {
111  if (deferred_installs_.empty() || !running_installers_.empty())
112    return;
113
114  const DeferredInstall& deferred = deferred_installs_.front();
115  RunInstall(deferred.installer, deferred.path);
116  deferred_installs_.pop();
117}
118
119void InstallLimiter::RunInstall(const scoped_refptr<CrxInstaller>& installer,
120                                const base::FilePath& path) {
121  registrar_.Add(this,
122                 chrome::NOTIFICATION_CRX_INSTALLER_DONE,
123                 content::Source<CrxInstaller>(installer.get()));
124
125  installer->InstallCrx(path);
126  running_installers_.insert(installer);
127}
128
129void InstallLimiter::Observe(int type,
130                             const content::NotificationSource& source,
131                             const content::NotificationDetails& details) {
132  DCHECK_EQ(chrome::NOTIFICATION_CRX_INSTALLER_DONE, type);
133
134  registrar_.Remove(this,
135                    chrome::NOTIFICATION_CRX_INSTALLER_DONE,
136                    source);
137
138  const scoped_refptr<CrxInstaller> installer =
139      content::Source<extensions::CrxInstaller>(source).ptr();
140  running_installers_.erase(installer);
141  CheckAndRunDeferrredInstalls();
142}
143
144}  // namespace extensions
145