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