summaryrefslogtreecommitdiff
path: root/chromium/components/download
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/download')
-rw-r--r--chromium/components/download/BUILD.gn23
-rw-r--r--chromium/components/download/components_unittests.filter14
-rw-r--r--chromium/components/download/content/BUILD.gn38
-rw-r--r--chromium/components/download/content/DEPS1
-rw-r--r--chromium/components/download/content/download_driver_impl.cc188
-rw-r--r--chromium/components/download/content/download_driver_impl.h67
-rw-r--r--chromium/components/download/content/download_driver_impl_unittest.cc129
-rw-r--r--chromium/components/download/content/factory/BUILD.gn25
-rw-r--r--chromium/components/download/content/factory/download_service_factory.cc58
-rw-r--r--chromium/components/download/content/factory/download_service_factory.h43
-rw-r--r--chromium/components/download/content/internal/BUILD.gn45
-rw-r--r--chromium/components/download/content/internal/download_driver_impl.cc279
-rw-r--r--chromium/components/download/content/internal/download_driver_impl.h88
-rw-r--r--chromium/components/download/content/internal/download_driver_impl_unittest.cc189
-rw-r--r--chromium/components/download/content/public/BUILD.gn38
-rw-r--r--chromium/components/download/content/public/all_download_item_notifier.cc75
-rw-r--r--chromium/components/download/content/public/all_download_item_notifier.h94
-rw-r--r--chromium/components/download/content/public/all_download_item_notifier_unittest.cc114
-rw-r--r--chromium/components/download/internal/BUILD.gn55
-rw-r--r--chromium/components/download/internal/DEPS1
-rw-r--r--chromium/components/download/internal/client_set.cc30
-rw-r--r--chromium/components/download/internal/client_set.h35
-rw-r--r--chromium/components/download/internal/client_set_unittest.cc43
-rw-r--r--chromium/components/download/internal/config.cc77
-rw-r--r--chromium/components/download/internal/config.h47
-rw-r--r--chromium/components/download/internal/controller.h110
-rw-r--r--chromium/components/download/internal/controller_impl.cc1034
-rw-r--r--chromium/components/download/internal/controller_impl.h234
-rw-r--r--chromium/components/download/internal/controller_impl_unittest.cc1405
-rw-r--r--chromium/components/download/internal/download_driver.h49
-rw-r--r--chromium/components/download/internal/download_service_impl.cc155
-rw-r--r--chromium/components/download/internal/download_service_impl.h23
-rw-r--r--chromium/components/download/internal/download_service_impl_unittest.cc145
-rw-r--r--chromium/components/download/internal/download_store.cc113
-rw-r--r--chromium/components/download/internal/download_store.h60
-rw-r--r--chromium/components/download/internal/download_store_unittest.cc324
-rw-r--r--chromium/components/download/internal/driver_entry.h17
-rw-r--r--chromium/components/download/internal/entry.cc29
-rw-r--r--chromium/components/download/internal/entry.h26
-rw-r--r--chromium/components/download/internal/entry_utils.cc67
-rw-r--r--chromium/components/download/internal/entry_utils.h49
-rw-r--r--chromium/components/download/internal/entry_utils_unittest.cc119
-rw-r--r--chromium/components/download/internal/file_monitor.h58
-rw-r--r--chromium/components/download/internal/file_monitor_impl.cc219
-rw-r--r--chromium/components/download/internal/file_monitor_impl.h63
-rw-r--r--chromium/components/download/internal/file_monitor_unittest.cc173
-rw-r--r--chromium/components/download/internal/model.h19
-rw-r--r--chromium/components/download/internal/model_impl.cc78
-rw-r--r--chromium/components/download/internal/model_impl.h10
-rw-r--r--chromium/components/download/internal/model_impl_unittest.cc203
-rw-r--r--chromium/components/download/internal/noop_store.cc53
-rw-r--r--chromium/components/download/internal/noop_store.h46
-rw-r--r--chromium/components/download/internal/proto/BUILD.gn18
-rw-r--r--chromium/components/download/internal/proto/entry.proto55
-rw-r--r--chromium/components/download/internal/proto/request.proto21
-rw-r--r--chromium/components/download/internal/proto/scheduling.proto43
-rw-r--r--chromium/components/download/internal/proto_conversions.cc307
-rw-r--r--chromium/components/download/internal/proto_conversions.h64
-rw-r--r--chromium/components/download/internal/proto_conversions_unittest.cc153
-rw-r--r--chromium/components/download/internal/scheduler/battery_listener.cc58
-rw-r--r--chromium/components/download/internal/scheduler/battery_listener.h59
-rw-r--r--chromium/components/download/internal/scheduler/battery_listener_unittest.cc74
-rw-r--r--chromium/components/download/internal/scheduler/device_status.cc72
-rw-r--r--chromium/components/download/internal/scheduler/device_status.h60
-rw-r--r--chromium/components/download/internal/scheduler/device_status_listener.cc122
-rw-r--r--chromium/components/download/internal/scheduler/device_status_listener.h72
-rw-r--r--chromium/components/download/internal/scheduler/device_status_listener_unittest.cc146
-rw-r--r--chromium/components/download/internal/scheduler/network_listener.cc84
-rw-r--r--chromium/components/download/internal/scheduler/network_listener.h66
-rw-r--r--chromium/components/download/internal/scheduler/network_listener_unittest.cc100
-rw-r--r--chromium/components/download/internal/scheduler/scheduler.h36
-rw-r--r--chromium/components/download/internal/scheduler/scheduler_impl.cc131
-rw-r--r--chromium/components/download/internal/scheduler/scheduler_impl.h72
-rw-r--r--chromium/components/download/internal/scheduler/scheduler_impl_unittest.cc429
-rw-r--r--chromium/components/download/internal/service_config_impl.cc24
-rw-r--r--chromium/components/download/internal/service_config_impl.h32
-rw-r--r--chromium/components/download/internal/service_config_impl_unittest.cc23
-rw-r--r--chromium/components/download/internal/startup_status.cc28
-rw-r--r--chromium/components/download/internal/startup_status.h40
-rw-r--r--chromium/components/download/internal/stats.cc310
-rw-r--r--chromium/components/download/internal/stats.h191
-rw-r--r--chromium/components/download/internal/store.h7
-rw-r--r--chromium/components/download/internal/test/BUILD.gn12
-rw-r--r--chromium/components/download/public/BUILD.gn22
-rw-r--r--chromium/components/download/public/client.h57
-rw-r--r--chromium/components/download/public/clients.h21
-rw-r--r--chromium/components/download/public/download_params.cc9
-rw-r--r--chromium/components/download/public/download_params.h35
-rw-r--r--chromium/components/download/public/download_service.h56
-rw-r--r--chromium/components/download/public/download_task_types.h22
-rw-r--r--chromium/components/download/public/service_config.h37
-rw-r--r--chromium/components/download/public/task_scheduler.h36
92 files changed, 8701 insertions, 1180 deletions
diff --git a/chromium/components/download/BUILD.gn b/chromium/components/download/BUILD.gn
index 618a7055c8b..441c1be542d 100644
--- a/chromium/components/download/BUILD.gn
+++ b/chromium/components/download/BUILD.gn
@@ -2,25 +2,24 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-if (is_android) {
- import("//build/config/android/config.gni")
- import("//build/config/android/rules.gni")
-}
+# TODO(dtrainor): Remove this file and have components_unittests depend on
+# //components/download/internal:unit_tests directly.
+group("unit_tests") {
+ testonly = true
-group("download") {
- public_deps = [
- "//components/download/public",
+ deps = [
+ "//components/download/internal:unit_tests",
]
- deps = [
- "//components/download/internal",
+ data_deps = [
+ ":components_unittests_gtest_filter",
]
}
-group("unit_tests") {
+source_set("components_unittests_gtest_filter") {
testonly = true
- deps = [
- "//components/download/internal:unit_tests",
+ data = [
+ "components_unittests.filter",
]
}
diff --git a/chromium/components/download/components_unittests.filter b/chromium/components/download/components_unittests.filter
new file mode 100644
index 00000000000..f694bb67279
--- /dev/null
+++ b/chromium/components/download/components_unittests.filter
@@ -0,0 +1,14 @@
+AllDownloadItemNotifierTest.*
+DeviceStatusListenerTest.*
+DownloadDriverImplTest.*
+DownloadSchedulerImplTest.*
+DownloadServiceClientSetTest.*
+DownloadServiceControllerImplTest.*
+DownloadServiceImplTest.*
+DownloadServiceEntryUtilsTest.*
+DownloadServiceModelImplTest.*
+DownloadStoreTest.*
+FileMonitorTest.*
+NetworkListenerTest.*
+ProtoConversionsTest.*
+ServiceConfigImplTest.* \ No newline at end of file
diff --git a/chromium/components/download/content/BUILD.gn b/chromium/components/download/content/BUILD.gn
index 7ca79f683b1..049294f31a1 100644
--- a/chromium/components/download/content/BUILD.gn
+++ b/chromium/components/download/content/BUILD.gn
@@ -2,41 +2,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-if (is_android) {
- import("//build/config/android/config.gni")
- import("//build/config/android/rules.gni")
-}
-
-source_set("content") {
- sources = [
- "download_driver_impl.cc",
- "download_driver_impl.h",
- ]
-
- public_deps = [
- "//components/download/internal",
- "//components/download/public",
- ]
-
- deps = [
- "//base",
- "//content/public/browser",
- "//net",
- "//url",
- ]
-}
-
-source_set("unit_tests") {
+# TODO(dtrainor): Remove this file and have components_unittests depend on
+# //components/download/content/internal:unit_tests directly.
+group("unit_tests") {
testonly = true
- sources = [
- "download_driver_impl_unittest.cc",
- ]
-
deps = [
- ":content",
- "//content/test:test_support",
- "//testing/gmock",
- "//testing/gtest",
+ "//components/download/content/internal:unit_tests",
+ "//components/download/content/public:unit_tests",
]
}
diff --git a/chromium/components/download/content/DEPS b/chromium/components/download/content/DEPS
index ea159c83af6..b72efab3087 100644
--- a/chromium/components/download/content/DEPS
+++ b/chromium/components/download/content/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+components/leveldb_proto",
"+content/public/browser",
"+content/public/test",
"+base",
diff --git a/chromium/components/download/content/download_driver_impl.cc b/chromium/components/download/content/download_driver_impl.cc
deleted file mode 100644
index 0d1d2811430..00000000000
--- a/chromium/components/download/content/download_driver_impl.cc
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/content/download_driver_impl.h"
-
-#include "components/download/internal/driver_entry.h"
-#include "content/public/browser/download_interrupt_reasons.h"
-#include "content/public/browser/download_url_parameters.h"
-#include "content/public/browser/storage_partition.h"
-#include "net/http/http_response_headers.h"
-
-namespace download {
-
-namespace {
-
-// Converts a content::DownloadItem::DownloadState to DriverEntry::State.
-DriverEntry::State ToDriverEntryState(
- content::DownloadItem::DownloadState state) {
- switch (state) {
- case content::DownloadItem::IN_PROGRESS:
- return DriverEntry::State::IN_PROGRESS;
- case content::DownloadItem::COMPLETE:
- return DriverEntry::State::COMPLETE;
- case content::DownloadItem::CANCELLED:
- return DriverEntry::State::CANCELLED;
- case content::DownloadItem::INTERRUPTED:
- return DriverEntry::State::INTERRUPTED;
- case content::DownloadItem::MAX_DOWNLOAD_STATE:
- return DriverEntry::State::UNKNOWN;
- default:
- NOTREACHED();
- return DriverEntry::State::UNKNOWN;
- }
-}
-
-} // namespace
-
-// static
-DriverEntry DownloadDriverImpl::CreateDriverEntry(
- const content::DownloadItem* item) {
- DCHECK(item);
- DriverEntry entry;
- entry.guid = item->GetGuid();
- entry.state = ToDriverEntryState(item->GetState());
- entry.paused = item->IsPaused();
- entry.bytes_downloaded = item->GetReceivedBytes();
- entry.expected_total_size = item->GetTotalBytes();
- entry.response_headers = item->GetResponseHeaders();
- return entry;
-}
-
-DownloadDriverImpl::DownloadDriverImpl(content::DownloadManager* manager,
- const base::FilePath& dir)
- : download_manager_(manager), file_dir_(dir), client_(nullptr) {
- DCHECK(download_manager_);
-}
-
-DownloadDriverImpl::~DownloadDriverImpl() {
- if (download_manager_)
- download_manager_->RemoveObserver(this);
-}
-
-void DownloadDriverImpl::Initialize(DownloadDriver::Client* client) {
- DCHECK(!client_);
- client_ = client;
- DCHECK(client_);
-
- // |download_manager_| may be shut down. Informs the client.
- if (!download_manager_) {
- client_->OnDriverReady(false);
- return;
- }
-
- download_manager_->AddObserver(this);
- if (download_manager_->IsManagerInitialized())
- client_->OnDriverReady(true);
-}
-
-bool DownloadDriverImpl::IsReady() const {
- return client_ && download_manager_;
-}
-
-void DownloadDriverImpl::Start(const DownloadParams& params) {
- DCHECK(!params.request_params.url.is_empty());
- DCHECK(!params.guid.empty());
- if (!download_manager_)
- return;
-
- content::StoragePartition* storage_partition =
- content::BrowserContext::GetStoragePartitionForSite(
- download_manager_->GetBrowserContext(), params.request_params.url);
- DCHECK(storage_partition);
-
- std::unique_ptr<content::DownloadUrlParameters> download_url_params(
- new content::DownloadUrlParameters(
- params.request_params.url,
- storage_partition->GetURLRequestContext()));
-
- // TODO(xingliu): Handle the request headers from |params|, need to tweak
- // download network stack.
- // Make content::DownloadManager handle potential guid collision and return
- // an error to fail the download cleanly.
- download_url_params->set_guid(params.guid);
- download_url_params->set_transient(true);
- download_url_params->set_method(params.request_params.method);
- download_url_params->set_file_path(file_dir_.AppendASCII(params.guid));
-
- download_manager_->DownloadUrl(std::move(download_url_params));
-}
-
-void DownloadDriverImpl::Cancel(const std::string& guid) {
- if (!download_manager_)
- return;
- content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
- // Cancels the download and removes the persisted records in content layer.
- if (item) {
- item->RemoveObserver(this);
- item->Remove();
- }
-}
-
-void DownloadDriverImpl::Pause(const std::string& guid) {
- if (!download_manager_)
- return;
- content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
- if (item)
- item->Pause();
-}
-
-void DownloadDriverImpl::Resume(const std::string& guid) {
- if (!download_manager_)
- return;
- content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
- if (item)
- item->Resume();
-}
-
-base::Optional<DriverEntry> DownloadDriverImpl::Find(const std::string& guid) {
- if (!download_manager_)
- return base::nullopt;
- content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
- if (item)
- return CreateDriverEntry(item);
- return base::nullopt;
-}
-
-void DownloadDriverImpl::OnDownloadUpdated(content::DownloadItem* item) {
- DCHECK(client_);
-
- using DownloadState = content::DownloadItem::DownloadState;
- DownloadState state = item->GetState();
- content::DownloadInterruptReason reason = item->GetLastReason();
- DriverEntry entry = CreateDriverEntry(item);
-
- if (state == DownloadState::COMPLETE) {
- client_->OnDownloadSucceeded(entry, item->GetTargetFilePath());
- item->RemoveObserver(this);
- } else if (state == DownloadState::IN_PROGRESS) {
- client_->OnDownloadUpdated(entry);
- } else if (reason !=
- content::DownloadInterruptReason::DOWNLOAD_INTERRUPT_REASON_NONE) {
- client_->OnDownloadFailed(entry, static_cast<int>(reason));
- item->RemoveObserver(this);
- }
-}
-
-void DownloadDriverImpl::OnDownloadCreated(content::DownloadManager* manager,
- content::DownloadItem* item) {
- // Listens to all downloads.
- item->AddObserver(this);
- DCHECK(client_);
- DriverEntry entry = CreateDriverEntry(item);
- client_->OnDownloadCreated(entry);
-}
-
-void DownloadDriverImpl::OnManagerInitialized() {
- DCHECK(client_);
- DCHECK(download_manager_);
- client_->OnDriverReady(true);
-}
-
-void DownloadDriverImpl::ManagerGoingDown(content::DownloadManager* manager) {
- DCHECK_EQ(download_manager_, manager);
- download_manager_ = nullptr;
-}
-
-} // namespace download
diff --git a/chromium/components/download/content/download_driver_impl.h b/chromium/components/download/content/download_driver_impl.h
deleted file mode 100644
index c7c5b1ce1b2..00000000000
--- a/chromium/components/download/content/download_driver_impl.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOWNLOAD_CONTENT_DOWNLOAD_DRIVER_IMPL_H_
-#define COMPONENTS_DOWNLOAD_CONTENT_DOWNLOAD_DRIVER_IMPL_H_
-
-#include <string>
-
-#include "base/files/file_path.h"
-#include "components/download/internal/download_driver.h"
-#include "components/download/public/download_params.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/download_manager.h"
-
-namespace download {
-
-struct DriverEntry;
-
-// Aggregates and handles all interaction between download service and content
-// download logic.
-class DownloadDriverImpl : public DownloadDriver,
- public content::DownloadManager::Observer,
- public content::DownloadItem::Observer {
- public:
- // Creates a driver entry based on a download item.
- static DriverEntry CreateDriverEntry(const content::DownloadItem* item);
-
- // Create the driver. All files downloaded will be saved to |dir|.
- DownloadDriverImpl(content::DownloadManager* manager,
- const base::FilePath& dir);
- ~DownloadDriverImpl() override;
-
- // DownloadDriver implementation.
- void Initialize(DownloadDriver::Client* client) override;
- bool IsReady() const override;
- void Start(const DownloadParams& params) override;
- void Cancel(const std::string& guid) override;
- void Pause(const std::string& guid) override;
- void Resume(const std::string& guid) override;
- base::Optional<DriverEntry> Find(const std::string& guid) override;
-
- private:
- // content::DownloadItem::Observer implementation.
- void OnDownloadUpdated(content::DownloadItem* item) override;
-
- // content::DownloadManager::Observer implementation.
- void OnDownloadCreated(content::DownloadManager* manager,
- content::DownloadItem* item) override;
- void OnManagerInitialized() override;
- void ManagerGoingDown(content::DownloadManager* manager) override;
-
- // Low level download handle.
- content::DownloadManager* download_manager_;
-
- // Target directory of download files.
- base::FilePath file_dir_;
-
- // The client that receives updates from low level download logic.
- DownloadDriver::Client* client_;
-
- DISALLOW_COPY_AND_ASSIGN(DownloadDriverImpl);
-};
-
-} // namespace download
-
-#endif // COMPONENTS_DOWNLOAD_CONTENT_DOWNLOAD_DRIVER_IMPL_H_
diff --git a/chromium/components/download/content/download_driver_impl_unittest.cc b/chromium/components/download/content/download_driver_impl_unittest.cc
deleted file mode 100644
index c53b490fac4..00000000000
--- a/chromium/components/download/content/download_driver_impl_unittest.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/content/download_driver_impl.h"
-
-#include <memory>
-#include <string>
-
-#include "base/memory/ptr_util.h"
-#include "content/public/test/fake_download_item.h"
-#include "content/public/test/mock_download_manager.h"
-#include "net/http/http_response_headers.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using testing::_;
-using testing::NiceMock;
-using testing::Return;
-
-namespace download {
-
-namespace {
-
-const char kFakeGuid[] = "fake_guid";
-
-// Matcher to compare driver entries. Not all the memeber fields are compared.
-// Currently no comparison in non test code, so no operator== override for
-// driver entry.
-MATCHER_P(DriverEntryEuqual, entry, "") {
- return entry.guid == arg.guid && entry.state == arg.state &&
- entry.bytes_downloaded == arg.bytes_downloaded;
-}
-
-} // namespace
-
-class MockDriverClient : public DownloadDriver::Client {
- public:
- MOCK_METHOD1(OnDriverReady, void(bool));
- MOCK_METHOD1(OnDownloadCreated, void(const DriverEntry&));
- MOCK_METHOD2(OnDownloadFailed, void(const DriverEntry&, int));
- MOCK_METHOD2(OnDownloadSucceeded,
- void(const DriverEntry&, const base::FilePath&));
- MOCK_METHOD1(OnDownloadUpdated, void(const DriverEntry&));
-};
-
-class DownloadDriverImplTest : public testing::Test {
- public:
- DownloadDriverImplTest() = default;
- ~DownloadDriverImplTest() override = default;
-
- void SetUp() override {
- driver_ =
- base::MakeUnique<DownloadDriverImpl>(&mock_manager_, base::FilePath());
- }
-
- // TODO(xingliu): implements test download manager for embedders to test.
- NiceMock<content::MockDownloadManager> mock_manager_;
- MockDriverClient mock_client_;
- std::unique_ptr<DownloadDriverImpl> driver_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DownloadDriverImplTest);
-};
-
-// Ensure the download manager can be initialized after the download driver.
-TEST_F(DownloadDriverImplTest, ManagerLateInitialization) {
- EXPECT_CALL(mock_manager_, IsManagerInitialized())
- .Times(1)
- .WillOnce(Return(false));
- driver_->Initialize(&mock_client_);
-
- EXPECT_CALL(mock_client_, OnDriverReady(true));
- static_cast<content::DownloadManager::Observer*>(driver_.get())
- ->OnManagerInitialized();
-}
-
-// Ensures download updates from download items are propagated correctly.
-TEST_F(DownloadDriverImplTest, DownloadItemUpdateEvents) {
- using DownloadState = content::DownloadItem::DownloadState;
- using DownloadInterruptReason = content::DownloadInterruptReason;
-
- EXPECT_CALL(mock_manager_, IsManagerInitialized())
- .Times(1)
- .WillOnce(Return(true));
- EXPECT_CALL(mock_client_, OnDriverReady(true)).Times(1);
- driver_->Initialize(&mock_client_);
-
- content::FakeDownloadItem fake_item;
- fake_item.SetState(DownloadState::IN_PROGRESS);
- fake_item.SetGuid(kFakeGuid);
- fake_item.SetReceivedBytes(0);
- fake_item.SetTotalBytes(1024);
- DriverEntry entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
-
- EXPECT_CALL(mock_client_, OnDownloadUpdated(DriverEntryEuqual(entry)))
- .Times(1)
- .RetiresOnSaturation();
- static_cast<content::DownloadItem::Observer*>(driver_.get())
- ->OnDownloadUpdated(&fake_item);
-
- // Nothing happens for cancelled state.
- fake_item.SetState(DownloadState::CANCELLED);
- static_cast<content::DownloadItem::Observer*>(driver_.get())
- ->OnDownloadUpdated(&fake_item);
-
- fake_item.SetReceivedBytes(1024);
- fake_item.SetState(DownloadState::COMPLETE);
- entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
- EXPECT_CALL(mock_client_, OnDownloadSucceeded(DriverEntryEuqual(entry), _))
- .Times(1)
- .RetiresOnSaturation();
- static_cast<content::DownloadItem::Observer*>(driver_.get())
- ->OnDownloadUpdated(&fake_item);
-
- fake_item.SetState(DownloadState::INTERRUPTED);
- fake_item.SetLastReason(
- DownloadInterruptReason::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT);
- entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
- int reason = static_cast<int>(
- DownloadInterruptReason::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT);
- EXPECT_CALL(mock_client_, OnDownloadFailed(DriverEntryEuqual(entry), reason))
- .Times(1)
- .RetiresOnSaturation();
- static_cast<content::DownloadItem::Observer*>(driver_.get())
- ->OnDownloadUpdated(&fake_item);
-}
-
-} // namespace download
diff --git a/chromium/components/download/content/factory/BUILD.gn b/chromium/components/download/content/factory/BUILD.gn
new file mode 100644
index 00000000000..46f834f0296
--- /dev/null
+++ b/chromium/components/download/content/factory/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("factory") {
+ sources = [
+ "download_service_factory.cc",
+ "download_service_factory.h",
+ ]
+
+ public_deps = [
+ "//components/download/public",
+ ]
+
+ deps = [
+ "//base",
+ "//components/download/content/internal",
+ "//components/download/internal",
+ "//components/download/internal/proto",
+ "//components/leveldb_proto",
+ "//content/public/browser",
+ "//net",
+ "//url",
+ ]
+}
diff --git a/chromium/components/download/content/factory/download_service_factory.cc b/chromium/components/download/content/factory/download_service_factory.cc
new file mode 100644
index 00000000000..95fbf6ade1d
--- /dev/null
+++ b/chromium/components/download/content/factory/download_service_factory.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/content/internal/download_driver_impl.h"
+#include "components/download/internal/client_set.h"
+#include "components/download/internal/config.h"
+#include "components/download/internal/controller_impl.h"
+#include "components/download/internal/download_service_impl.h"
+#include "components/download/internal/download_store.h"
+#include "components/download/internal/file_monitor_impl.h"
+#include "components/download/internal/model_impl.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/scheduler/scheduler_impl.h"
+#include "components/leveldb_proto/proto_database_impl.h"
+
+namespace download {
+namespace {
+const base::FilePath::CharType kEntryDBStorageDir[] =
+ FILE_PATH_LITERAL("EntryDB");
+const base::FilePath::CharType kFilesStorageDir[] = FILE_PATH_LITERAL("Files");
+} // namespace
+
+DownloadService* CreateDownloadService(
+ std::unique_ptr<DownloadClientMap> clients,
+ content::DownloadManager* download_manager,
+ const base::FilePath& storage_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ std::unique_ptr<TaskScheduler> task_scheduler) {
+ auto client_set = base::MakeUnique<ClientSet>(std::move(clients));
+ auto config = Configuration::CreateFromFinch();
+
+ auto files_storage_dir = storage_dir.Append(kFilesStorageDir);
+ auto driver = base::MakeUnique<DownloadDriverImpl>(download_manager);
+
+ auto entry_db_storage_dir = storage_dir.Append(kEntryDBStorageDir);
+ auto entry_db =
+ base::MakeUnique<leveldb_proto::ProtoDatabaseImpl<protodb::Entry>>(
+ background_task_runner);
+ auto store = base::MakeUnique<DownloadStore>(entry_db_storage_dir,
+ std::move(entry_db));
+ auto model = base::MakeUnique<ModelImpl>(std::move(store));
+ auto device_status_listener =
+ base::MakeUnique<DeviceStatusListener>(config->network_change_delay);
+ auto scheduler = base::MakeUnique<SchedulerImpl>(
+ task_scheduler.get(), config.get(), client_set.get());
+ auto file_monitor = base::MakeUnique<FileMonitorImpl>(
+ files_storage_dir, background_task_runner, config->file_keep_alive_time);
+ auto controller = base::MakeUnique<ControllerImpl>(
+ config.get(), std::move(client_set), std::move(driver), std::move(model),
+ std::move(device_status_listener), std::move(scheduler),
+ std::move(task_scheduler), std::move(file_monitor), files_storage_dir);
+ return new DownloadServiceImpl(std::move(config), std::move(controller));
+}
+
+} // namespace download
diff --git a/chromium/components/download/content/factory/download_service_factory.h b/chromium/components/download/content/factory/download_service_factory.h
new file mode 100644
index 00000000000..8df6dfe7da5
--- /dev/null
+++ b/chromium/components/download/content/factory/download_service_factory.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
+#define COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "components/download/public/clients.h"
+
+namespace content {
+class DownloadManager;
+} // namespace content
+
+namespace download {
+
+class DownloadService;
+class TaskScheduler;
+
+// |clients| is a map of DownloadClient -> std::unique_ptr<Client>. This
+// represents all of the clients that are allowed to have requests made on
+// their behalf. This cannot be changed after startup. Any existing requests
+// no longer associated with a client will be cancelled.
+// |storage_dir| is a path to where all the local storage will be. This will
+// hold the internal database as well as any temporary files on disk. If this
+// is an empty path, the service will not persist any information to disk and
+// will act as an in-memory only service (this means no auto-retries after
+// restarts, no files written on completion, etc.).
+// |background_task_runner| will be used for all disk reads and writes.
+DownloadService* CreateDownloadService(
+ std::unique_ptr<DownloadClientMap> clients,
+ content::DownloadManager* download_manager,
+ const base::FilePath& storage_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ std::unique_ptr<TaskScheduler> task_scheduler);
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_CONTENT_FACTORY_DOWNLOAD_SERVICE_FACTORY_H_
diff --git a/chromium/components/download/content/internal/BUILD.gn b/chromium/components/download/content/internal/BUILD.gn
new file mode 100644
index 00000000000..77025b91da8
--- /dev/null
+++ b/chromium/components/download/content/internal/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("internal") {
+ visibility = [
+ ":*",
+ "//components/download/content/factory",
+ ]
+
+ sources = [
+ "download_driver_impl.cc",
+ "download_driver_impl.h",
+ ]
+
+ public_deps = [
+ "//components/download/content/public",
+ "//components/download/internal",
+ "//components/download/public",
+ ]
+
+ deps = [
+ "//base",
+ "//content/public/browser",
+ "//net",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+
+ sources = [
+ "download_driver_impl_unittest.cc",
+ ]
+
+ deps = [
+ ":internal",
+ "//base/test:test_support",
+ "//components/download/content/public",
+ "//content/test:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/download/content/internal/download_driver_impl.cc b/chromium/components/download/content/internal/download_driver_impl.cc
new file mode 100644
index 00000000000..99686e18454
--- /dev/null
+++ b/chromium/components/download/content/internal/download_driver_impl.cc
@@ -0,0 +1,279 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/content/internal/download_driver_impl.h"
+
+#include <set>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/internal/driver_entry.h"
+#include "content/public/browser/download_interrupt_reasons.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/http/http_response_headers.h"
+
+namespace download {
+
+namespace {
+
+// Converts a content::DownloadItem::DownloadState to DriverEntry::State.
+DriverEntry::State ToDriverEntryState(
+ content::DownloadItem::DownloadState state) {
+ switch (state) {
+ case content::DownloadItem::IN_PROGRESS:
+ return DriverEntry::State::IN_PROGRESS;
+ case content::DownloadItem::COMPLETE:
+ return DriverEntry::State::COMPLETE;
+ case content::DownloadItem::CANCELLED:
+ return DriverEntry::State::CANCELLED;
+ case content::DownloadItem::INTERRUPTED:
+ return DriverEntry::State::INTERRUPTED;
+ case content::DownloadItem::MAX_DOWNLOAD_STATE:
+ return DriverEntry::State::UNKNOWN;
+ default:
+ NOTREACHED();
+ return DriverEntry::State::UNKNOWN;
+ }
+}
+
+FailureType FailureTypeFromInterruptReason(
+ content::DownloadInterruptReason reason) {
+ switch (reason) {
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
+ case content::DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
+ case content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
+ case content::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM:
+ case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
+ return FailureType::NOT_RECOVERABLE;
+ default:
+ return FailureType::RECOVERABLE;
+ }
+}
+
+// Logs interrupt reason when download fails.
+void LogDownloadInterruptReason(content::DownloadInterruptReason reason) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Download.Service.Driver.InterruptReason",
+ reason);
+}
+
+} // namespace
+
+// static
+DriverEntry DownloadDriverImpl::CreateDriverEntry(
+ const content::DownloadItem* item) {
+ DCHECK(item);
+ DriverEntry entry;
+ entry.guid = item->GetGuid();
+ entry.state = ToDriverEntryState(item->GetState());
+ entry.paused = item->IsPaused();
+ entry.bytes_downloaded = item->GetReceivedBytes();
+ entry.expected_total_size = item->GetTotalBytes();
+ entry.current_file_path =
+ item->GetState() == content::DownloadItem::DownloadState::COMPLETE
+ ? item->GetTargetFilePath()
+ : item->GetFullPath();
+ entry.completion_time = item->GetEndTime();
+ entry.response_headers = item->GetResponseHeaders();
+ entry.url_chain = item->GetUrlChain();
+ return entry;
+}
+
+DownloadDriverImpl::DownloadDriverImpl(content::DownloadManager* manager)
+ : download_manager_(manager), client_(nullptr), weak_ptr_factory_(this) {
+ DCHECK(download_manager_);
+}
+
+DownloadDriverImpl::~DownloadDriverImpl() = default;
+
+void DownloadDriverImpl::Initialize(DownloadDriver::Client* client) {
+ DCHECK(!client_);
+ client_ = client;
+ DCHECK(client_);
+
+ // |download_manager_| may be shut down. Informs the client.
+ if (!download_manager_) {
+ client_->OnDriverReady(false);
+ return;
+ }
+
+ notifier_ =
+ base::MakeUnique<AllDownloadItemNotifier>(download_manager_, this);
+}
+
+void DownloadDriverImpl::HardRecover() {
+ // TODO(dtrainor, xingliu): Implement recovery for the DownloadManager.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&DownloadDriverImpl::OnHardRecoverComplete,
+ weak_ptr_factory_.GetWeakPtr(), true));
+}
+
+bool DownloadDriverImpl::IsReady() const {
+ return client_ && download_manager_ &&
+ download_manager_->IsManagerInitialized();
+}
+
+void DownloadDriverImpl::Start(
+ const RequestParams& request_params,
+ const std::string& guid,
+ const base::FilePath& file_path,
+ const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+ DCHECK(!request_params.url.is_empty());
+ DCHECK(!guid.empty());
+ if (!download_manager_)
+ return;
+
+ content::StoragePartition* storage_partition =
+ content::BrowserContext::GetStoragePartitionForSite(
+ download_manager_->GetBrowserContext(), request_params.url);
+ DCHECK(storage_partition);
+
+ std::unique_ptr<content::DownloadUrlParameters> download_url_params(
+ new content::DownloadUrlParameters(
+ request_params.url, storage_partition->GetURLRequestContext(),
+ traffic_annotation));
+
+ // TODO(xingliu): Make content::DownloadManager handle potential guid
+ // collision and return an error to fail the download cleanly.
+ for (net::HttpRequestHeaders::Iterator it(request_params.request_headers);
+ it.GetNext();) {
+ download_url_params->add_request_header(it.name(), it.value());
+ }
+ download_url_params->set_guid(guid);
+ download_url_params->set_transient(true);
+ download_url_params->set_method(request_params.method);
+ download_url_params->set_file_path(file_path);
+
+ download_manager_->DownloadUrl(std::move(download_url_params));
+}
+
+void DownloadDriverImpl::Remove(const std::string& guid) {
+ guid_to_remove_.emplace(guid);
+
+ // DownloadItem::Remove will cause the item object removed from memory, post
+ // the remove task to avoid the object being accessed in the same call stack.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&DownloadDriverImpl::DoRemoveDownload,
+ weak_ptr_factory_.GetWeakPtr(), guid));
+}
+
+void DownloadDriverImpl::DoRemoveDownload(const std::string& guid) {
+ if (!download_manager_)
+ return;
+ content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
+ // Cancels the download and removes the persisted records in content layer.
+ if (item) {
+ item->Remove();
+ }
+}
+
+void DownloadDriverImpl::Pause(const std::string& guid) {
+ if (!download_manager_)
+ return;
+ content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
+ if (item)
+ item->Pause();
+}
+
+void DownloadDriverImpl::Resume(const std::string& guid) {
+ if (!download_manager_)
+ return;
+ content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
+ if (item)
+ item->Resume();
+}
+
+base::Optional<DriverEntry> DownloadDriverImpl::Find(const std::string& guid) {
+ if (!download_manager_)
+ return base::nullopt;
+ content::DownloadItem* item = download_manager_->GetDownloadByGuid(guid);
+ if (item)
+ return CreateDriverEntry(item);
+ return base::nullopt;
+}
+
+std::set<std::string> DownloadDriverImpl::GetActiveDownloads() {
+ std::set<std::string> guids;
+ if (!download_manager_)
+ return guids;
+
+ std::vector<content::DownloadItem*> items;
+ download_manager_->GetAllDownloads(&items);
+
+ for (auto* item : items) {
+ DriverEntry::State state = ToDriverEntryState(item->GetState());
+ if (state == DriverEntry::State::IN_PROGRESS)
+ guids.insert(item->GetGuid());
+ }
+
+ return guids;
+}
+
+void DownloadDriverImpl::OnDownloadUpdated(content::DownloadManager* manager,
+ content::DownloadItem* item) {
+ DCHECK(client_);
+ // Blocks the observer call if we asked to remove the download.
+ if (guid_to_remove_.find(item->GetGuid()) != guid_to_remove_.end())
+ return;
+
+ using DownloadState = content::DownloadItem::DownloadState;
+ DownloadState state = item->GetState();
+ content::DownloadInterruptReason reason = item->GetLastReason();
+ DriverEntry entry = CreateDriverEntry(item);
+
+ if (state == DownloadState::COMPLETE) {
+ client_->OnDownloadSucceeded(entry);
+ } else if (state == DownloadState::IN_PROGRESS) {
+ client_->OnDownloadUpdated(entry);
+ } else if (reason !=
+ content::DownloadInterruptReason::DOWNLOAD_INTERRUPT_REASON_NONE) {
+ LogDownloadInterruptReason(reason);
+ client_->OnDownloadFailed(entry, FailureTypeFromInterruptReason(reason));
+ }
+}
+
+void DownloadDriverImpl::OnDownloadRemoved(content::DownloadManager* manager,
+ content::DownloadItem* download) {
+ guid_to_remove_.erase(download->GetGuid());
+ // |download| is about to be deleted.
+}
+
+void DownloadDriverImpl::OnDownloadCreated(content::DownloadManager* manager,
+ content::DownloadItem* item) {
+ // Listens to all downloads.
+ DCHECK(client_);
+ DriverEntry entry = CreateDriverEntry(item);
+
+ // Only notifies the client about new downloads. Existing download data will
+ // be loaded before the driver is ready.
+ if (IsReady())
+ client_->OnDownloadCreated(entry);
+}
+
+void DownloadDriverImpl::OnManagerInitialized(
+ content::DownloadManager* manager) {
+ DCHECK_EQ(download_manager_, manager);
+ DCHECK(client_);
+ DCHECK(download_manager_);
+ client_->OnDriverReady(true);
+}
+
+void DownloadDriverImpl::OnManagerGoingDown(content::DownloadManager* manager) {
+ DCHECK_EQ(download_manager_, manager);
+ notifier_.reset();
+ download_manager_ = nullptr;
+}
+
+void DownloadDriverImpl::OnHardRecoverComplete(bool success) {
+ client_->OnDriverHardRecoverComplete(success);
+}
+
+} // namespace download
diff --git a/chromium/components/download/content/internal/download_driver_impl.h b/chromium/components/download/content/internal/download_driver_impl.h
new file mode 100644
index 00000000000..e634862041f
--- /dev/null
+++ b/chromium/components/download/content/internal/download_driver_impl.h
@@ -0,0 +1,88 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_CONTENT_INTERNAL_DOWNLOAD_DRIVER_IMPL_H_
+#define COMPONENTS_DOWNLOAD_CONTENT_INTERNAL_DOWNLOAD_DRIVER_IMPL_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "components/download/content/public/all_download_item_notifier.h"
+#include "components/download/internal/download_driver.h"
+#include "components/download/public/download_params.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_manager.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace download {
+
+struct DriverEntry;
+
+// Aggregates and handles all interaction between download service and content
+// download logic.
+class DownloadDriverImpl : public DownloadDriver,
+ public AllDownloadItemNotifier::Observer {
+ public:
+ // Creates a driver entry based on a download item.
+ static DriverEntry CreateDriverEntry(const content::DownloadItem* item);
+
+ // Create the driver.
+ DownloadDriverImpl(content::DownloadManager* manager);
+ ~DownloadDriverImpl() override;
+
+ // DownloadDriver implementation.
+ void Initialize(DownloadDriver::Client* client) override;
+ void HardRecover() override;
+ bool IsReady() const override;
+ void Start(
+ const RequestParams& request_params,
+ const std::string& guid,
+ const base::FilePath& file_path,
+ const net::NetworkTrafficAnnotationTag& traffic_annotation) override;
+ void Remove(const std::string& guid) override;
+ void Pause(const std::string& guid) override;
+ void Resume(const std::string& guid) override;
+ base::Optional<DriverEntry> Find(const std::string& guid) override;
+ std::set<std::string> GetActiveDownloads() override;
+
+ private:
+ // content::AllDownloadItemNotifier::Observer implementation.
+ void OnManagerInitialized(content::DownloadManager* manager) override;
+ void OnManagerGoingDown(content::DownloadManager* manager) override;
+ void OnDownloadCreated(content::DownloadManager* manager,
+ content::DownloadItem* item) override;
+ void OnDownloadUpdated(content::DownloadManager* manager,
+ content::DownloadItem* item) override;
+ void OnDownloadRemoved(content::DownloadManager* manager,
+ content::DownloadItem* item) override;
+
+ void OnHardRecoverComplete(bool success);
+
+ // Remove the download, used to be posted to the task queue.
+ void DoRemoveDownload(const std::string& guid);
+
+ // Low level download handle.
+ content::DownloadManager* download_manager_;
+
+ // The client that receives updates from low level download logic.
+ DownloadDriver::Client* client_;
+
+ // Built lazily on initialize and destroyed when/if the manager is torn down.
+ std::unique_ptr<AllDownloadItemNotifier> notifier_;
+
+ // Pending guid set of downloads that will be removed soon.
+ std::set<std::string> guid_to_remove_;
+
+ // Only used to post tasks on the same thread.
+ base::WeakPtrFactory<DownloadDriverImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadDriverImpl);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_CONTENT_INTERNAL_DOWNLOAD_DRIVER_IMPL_H_
diff --git a/chromium/components/download/content/internal/download_driver_impl_unittest.cc b/chromium/components/download/content/internal/download_driver_impl_unittest.cc
new file mode 100644
index 00000000000..f4937e02d99
--- /dev/null
+++ b/chromium/components/download/content/internal/download_driver_impl_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/content/internal/download_driver_impl.h"
+
+#include <memory>
+#include <string>
+
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/content/public/all_download_item_notifier.h"
+#include "content/public/test/fake_download_item.h"
+#include "content/public/test/mock_download_manager.h"
+#include "net/http/http_response_headers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+using testing::Return;
+
+namespace download {
+
+namespace {
+
+ACTION_P(PopulateVector, items) {
+ arg0->insert(arg0->begin(), items.begin(), items.end());
+}
+
+const char kFakeGuid[] = "fake_guid";
+
+// Matcher to compare driver entries. Not all the memeber fields are compared.
+// Currently no comparison in non test code, so no operator== override for
+// driver entry.
+MATCHER_P(DriverEntryEqual, entry, "") {
+ return entry.guid == arg.guid && entry.state == arg.state &&
+ entry.bytes_downloaded == arg.bytes_downloaded &&
+ entry.current_file_path.value() == arg.current_file_path.value();
+}
+
+} // namespace
+
+class MockDriverClient : public DownloadDriver::Client {
+ public:
+ MOCK_METHOD1(OnDriverReady, void(bool));
+ MOCK_METHOD1(OnDriverHardRecoverComplete, void(bool));
+ MOCK_METHOD1(OnDownloadCreated, void(const DriverEntry&));
+ MOCK_METHOD2(OnDownloadFailed, void(const DriverEntry&, FailureType));
+ MOCK_METHOD1(OnDownloadSucceeded, void(const DriverEntry&));
+ MOCK_METHOD1(OnDownloadUpdated, void(const DriverEntry&));
+};
+
+class DownloadDriverImplTest : public testing::Test {
+ public:
+ DownloadDriverImplTest()
+ : task_runner_(new base::TestSimpleTaskRunner), handle_(task_runner_) {}
+
+ ~DownloadDriverImplTest() override = default;
+
+ void SetUp() override {
+ driver_ = base::MakeUnique<DownloadDriverImpl>(&mock_manager_);
+ }
+
+ // TODO(xingliu): implements test download manager for embedders to test.
+ NiceMock<content::MockDownloadManager> mock_manager_;
+ MockDriverClient mock_client_;
+ std::unique_ptr<DownloadDriverImpl> driver_;
+
+ protected:
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle handle_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadDriverImplTest);
+};
+
+// Ensure the download manager can be initialized after the download driver.
+TEST_F(DownloadDriverImplTest, ManagerLateInitialization) {
+ EXPECT_CALL(mock_manager_, IsManagerInitialized())
+ .Times(1)
+ .WillOnce(Return(false));
+ driver_->Initialize(&mock_client_);
+
+ EXPECT_CALL(mock_client_, OnDriverReady(true));
+ static_cast<AllDownloadItemNotifier::Observer*>(driver_.get())
+ ->OnManagerInitialized(&mock_manager_);
+}
+
+TEST_F(DownloadDriverImplTest, TestHardRecover) {
+ EXPECT_CALL(mock_manager_, IsManagerInitialized())
+ .Times(1)
+ .WillOnce(Return(false));
+ driver_->Initialize(&mock_client_);
+
+ EXPECT_CALL(mock_client_, OnDriverHardRecoverComplete(true)).Times(1);
+ driver_->HardRecover();
+ task_runner_->RunUntilIdle();
+}
+
+// Ensures download updates from download items are propagated correctly.
+TEST_F(DownloadDriverImplTest, DownloadItemUpdateEvents) {
+ using DownloadState = content::DownloadItem::DownloadState;
+ using DownloadInterruptReason = content::DownloadInterruptReason;
+
+ EXPECT_CALL(mock_manager_, IsManagerInitialized())
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_client_, OnDriverReady(true)).Times(1);
+ driver_->Initialize(&mock_client_);
+
+ content::FakeDownloadItem fake_item;
+ fake_item.SetState(DownloadState::IN_PROGRESS);
+ fake_item.SetGuid(kFakeGuid);
+ fake_item.SetReceivedBytes(0);
+ fake_item.SetTotalBytes(1024);
+ DriverEntry entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
+
+ EXPECT_CALL(mock_client_, OnDownloadUpdated(DriverEntryEqual(entry)))
+ .Times(1)
+ .RetiresOnSaturation();
+ static_cast<AllDownloadItemNotifier::Observer*>(driver_.get())
+ ->OnDownloadUpdated(&mock_manager_, &fake_item);
+
+ // Nothing happens for cancelled state.
+ fake_item.SetState(DownloadState::CANCELLED);
+ static_cast<AllDownloadItemNotifier::Observer*>(driver_.get())
+ ->OnDownloadUpdated(&mock_manager_, &fake_item);
+
+ fake_item.SetReceivedBytes(1024);
+ fake_item.SetState(DownloadState::COMPLETE);
+ entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
+ EXPECT_CALL(mock_client_, OnDownloadSucceeded(DriverEntryEqual(entry)))
+ .Times(1)
+ .RetiresOnSaturation();
+ static_cast<AllDownloadItemNotifier::Observer*>(driver_.get())
+ ->OnDownloadUpdated(&mock_manager_, &fake_item);
+
+ fake_item.SetState(DownloadState::INTERRUPTED);
+ fake_item.SetLastReason(
+ DownloadInterruptReason::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT);
+ entry = DownloadDriverImpl::CreateDriverEntry(&fake_item);
+ EXPECT_CALL(mock_client_, OnDownloadFailed(DriverEntryEqual(entry),
+ FailureType::RECOVERABLE))
+ .Times(1)
+ .RetiresOnSaturation();
+ static_cast<AllDownloadItemNotifier::Observer*>(driver_.get())
+ ->OnDownloadUpdated(&mock_manager_, &fake_item);
+}
+
+TEST_F(DownloadDriverImplTest, TestGetActiveDownloadsCall) {
+ using DownloadState = content::DownloadItem::DownloadState;
+ content::FakeDownloadItem item1;
+ item1.SetState(DownloadState::IN_PROGRESS);
+ item1.SetGuid(base::GenerateGUID());
+
+ content::FakeDownloadItem item2;
+ item2.SetState(DownloadState::CANCELLED);
+ item2.SetGuid(base::GenerateGUID());
+
+ content::FakeDownloadItem item3;
+ item3.SetState(DownloadState::COMPLETE);
+ item3.SetGuid(base::GenerateGUID());
+
+ content::FakeDownloadItem item4;
+ item4.SetState(DownloadState::INTERRUPTED);
+ item4.SetGuid(base::GenerateGUID());
+
+ std::vector<content::DownloadItem*> items{&item1, &item2, &item3, &item4};
+
+ ON_CALL(mock_manager_, GetAllDownloads(_))
+ .WillByDefault(PopulateVector(items));
+
+ EXPECT_CALL(mock_manager_, IsManagerInitialized())
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_client_, OnDriverReady(true)).Times(1);
+ driver_->Initialize(&mock_client_);
+
+ auto guids = driver_->GetActiveDownloads();
+
+ EXPECT_EQ(1U, guids.size());
+ EXPECT_NE(guids.end(), guids.find(item1.GetGuid()));
+}
+
+} // namespace download
diff --git a/chromium/components/download/content/public/BUILD.gn b/chromium/components/download/content/public/BUILD.gn
new file mode 100644
index 00000000000..4cb80da314b
--- /dev/null
+++ b/chromium/components/download/content/public/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+ import("//build/config/android/rules.gni")
+}
+
+source_set("public") {
+ sources = [
+ "all_download_item_notifier.cc",
+ "all_download_item_notifier.h",
+ ]
+
+ public_deps = [
+ "//base",
+ ]
+
+ deps = [
+ "//content/public/browser",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+
+ sources = [
+ "all_download_item_notifier_unittest.cc",
+ ]
+
+ deps = [
+ ":public",
+ "//content/test:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/download/content/public/all_download_item_notifier.cc b/chromium/components/download/content/public/all_download_item_notifier.cc
new file mode 100644
index 00000000000..81159f57063
--- /dev/null
+++ b/chromium/components/download/content/public/all_download_item_notifier.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/content/public/all_download_item_notifier.h"
+
+namespace download {
+
+AllDownloadItemNotifier::AllDownloadItemNotifier(
+ content::DownloadManager* manager,
+ AllDownloadItemNotifier::Observer* observer)
+ : manager_(manager), observer_(observer) {
+ DCHECK(observer_);
+ manager_->AddObserver(this);
+ content::DownloadManager::DownloadVector items;
+ manager_->GetAllDownloads(&items);
+ for (content::DownloadManager::DownloadVector::const_iterator it =
+ items.begin();
+ it != items.end(); ++it) {
+ (*it)->AddObserver(this);
+ observing_.insert(*it);
+ }
+
+ if (manager_->IsManagerInitialized())
+ observer_->OnManagerInitialized(manager_);
+}
+
+AllDownloadItemNotifier::~AllDownloadItemNotifier() {
+ if (manager_)
+ manager_->RemoveObserver(this);
+ for (std::set<content::DownloadItem*>::const_iterator it = observing_.begin();
+ it != observing_.end(); ++it) {
+ (*it)->RemoveObserver(this);
+ }
+ observing_.clear();
+}
+
+void AllDownloadItemNotifier::OnManagerInitialized() {
+ observer_->OnManagerInitialized(manager_);
+}
+
+void AllDownloadItemNotifier::ManagerGoingDown(
+ content::DownloadManager* manager) {
+ DCHECK_EQ(manager_, manager);
+ observer_->OnManagerGoingDown(manager);
+ manager_->RemoveObserver(this);
+ manager_ = NULL;
+}
+
+void AllDownloadItemNotifier::OnDownloadCreated(
+ content::DownloadManager* manager,
+ content::DownloadItem* item) {
+ item->AddObserver(this);
+ observing_.insert(item);
+ observer_->OnDownloadCreated(manager, item);
+}
+
+void AllDownloadItemNotifier::OnDownloadUpdated(content::DownloadItem* item) {
+ observer_->OnDownloadUpdated(manager_, item);
+}
+
+void AllDownloadItemNotifier::OnDownloadOpened(content::DownloadItem* item) {
+ observer_->OnDownloadOpened(manager_, item);
+}
+
+void AllDownloadItemNotifier::OnDownloadRemoved(content::DownloadItem* item) {
+ observer_->OnDownloadRemoved(manager_, item);
+}
+
+void AllDownloadItemNotifier::OnDownloadDestroyed(content::DownloadItem* item) {
+ item->RemoveObserver(this);
+ observing_.erase(item);
+}
+
+} // namespace download
diff --git a/chromium/components/download/content/public/all_download_item_notifier.h b/chromium/components/download/content/public/all_download_item_notifier.h
new file mode 100644
index 00000000000..d52cc92543c
--- /dev/null
+++ b/chromium/components/download/content/public/all_download_item_notifier.h
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_ALL_DOWNLOAD_ITEM_NOTIFIER_H_
+#define COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_ALL_DOWNLOAD_ITEM_NOTIFIER_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager.h"
+
+// AllDownloadItemNotifier observes ALL the DownloadItems on a given
+// DownloadManager.
+// Clients should use GetManager() instead of storing their own pointer to the
+// manager so that they can be sensitive to managers that have gone down.
+
+// Example Usage:
+// class DownloadSystemConsumer : public AllDownloadItemNotifier::Observer {
+// public:
+// DownloadSystemConsumer(DownloadManager* original_manager,
+// DownloadManager* incognito_manager)
+// : original_notifier_(original_manager, this),
+// incognito_notifier_(incognito_manager, this) {
+// }
+//
+// virtual void OnDownloadUpdated(
+// DownloadManager* manager, DownloadItem* item) { ... }
+//
+// private:
+// AllDownloadItemNotifier original_notifier_;
+// AllDownloadItemNotifier incognito_notifier_;
+// };
+
+namespace download {
+
+class AllDownloadItemNotifier : public content::DownloadManager::Observer,
+ public content::DownloadItem::Observer {
+ public:
+ // All of the methods take the DownloadManager so that subclasses can observe
+ // multiple managers at once and easily distinguish which manager a given item
+ // belongs to.
+ class Observer {
+ public:
+ Observer() {}
+ virtual ~Observer() {}
+
+ virtual void OnManagerInitialized(content::DownloadManager* manager) {}
+ virtual void OnManagerGoingDown(content::DownloadManager* manager) {}
+ virtual void OnDownloadCreated(content::DownloadManager* manager,
+ content::DownloadItem* item) {}
+ virtual void OnDownloadUpdated(content::DownloadManager* manager,
+ content::DownloadItem* item) {}
+ virtual void OnDownloadOpened(content::DownloadManager* manager,
+ content::DownloadItem* item) {}
+ virtual void OnDownloadRemoved(content::DownloadManager* manager,
+ content::DownloadItem* item) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ AllDownloadItemNotifier(content::DownloadManager* manager,
+ Observer* observer);
+
+ ~AllDownloadItemNotifier() override;
+
+ // Returns NULL if the manager has gone down.
+ content::DownloadManager* GetManager() const { return manager_; }
+
+ private:
+ // DownloadManager::Observer
+ void OnManagerInitialized() override;
+ void ManagerGoingDown(content::DownloadManager* manager) override;
+ void OnDownloadCreated(content::DownloadManager* manager,
+ content::DownloadItem* item) override;
+
+ // DownloadItem::Observer
+ void OnDownloadUpdated(content::DownloadItem* item) override;
+ void OnDownloadOpened(content::DownloadItem* item) override;
+ void OnDownloadRemoved(content::DownloadItem* item) override;
+ void OnDownloadDestroyed(content::DownloadItem* item) override;
+
+ content::DownloadManager* manager_;
+ AllDownloadItemNotifier::Observer* observer_;
+ std::set<content::DownloadItem*> observing_;
+
+ DISALLOW_COPY_AND_ASSIGN(AllDownloadItemNotifier);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_ALL_DOWNLOAD_ITEM_NOTIFIER_H_
diff --git a/chromium/components/download/content/public/all_download_item_notifier_unittest.cc b/chromium/components/download/content/public/all_download_item_notifier_unittest.cc
new file mode 100644
index 00000000000..271fed869f7
--- /dev/null
+++ b/chromium/components/download/content/public/all_download_item_notifier_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/content/public/all_download_item_notifier.h"
+
+#include "base/macros.h"
+#include "content/public/test/mock_download_item.h"
+#include "content/public/test/mock_download_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::NiceMock;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace download {
+namespace {
+
+class MockNotifierObserver : public AllDownloadItemNotifier::Observer {
+ public:
+ MockNotifierObserver() {}
+ virtual ~MockNotifierObserver() {}
+
+ MOCK_METHOD2(OnDownloadCreated,
+ void(content::DownloadManager* manager,
+ content::DownloadItem* item));
+ MOCK_METHOD2(OnDownloadUpdated,
+ void(content::DownloadManager* manager,
+ content::DownloadItem* item));
+ MOCK_METHOD2(OnDownloadOpened,
+ void(content::DownloadManager* manager,
+ content::DownloadItem* item));
+ MOCK_METHOD2(OnDownloadRemoved,
+ void(content::DownloadManager* manager,
+ content::DownloadItem* item));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockNotifierObserver);
+};
+
+class AllDownloadItemNotifierTest : public testing::Test {
+ public:
+ AllDownloadItemNotifierTest()
+ : download_manager_(new content::MockDownloadManager) {}
+
+ virtual ~AllDownloadItemNotifierTest() {}
+
+ content::MockDownloadManager& manager() { return *download_manager_.get(); }
+
+ content::MockDownloadItem& item() { return item_; }
+
+ content::DownloadItem::Observer* NotifierAsItemObserver() const {
+ return notifier_.get();
+ }
+
+ content::DownloadManager::Observer* NotifierAsManagerObserver() const {
+ return notifier_.get();
+ }
+
+ MockNotifierObserver& observer() { return observer_; }
+
+ void SetNotifier() {
+ EXPECT_CALL(*download_manager_.get(), AddObserver(_));
+ notifier_.reset(
+ new AllDownloadItemNotifier(download_manager_.get(), &observer_));
+ }
+
+ void ClearNotifier() { notifier_.reset(); }
+
+ private:
+ NiceMock<content::MockDownloadItem> item_;
+ std::unique_ptr<content::MockDownloadManager> download_manager_;
+ std::unique_ptr<AllDownloadItemNotifier> notifier_;
+ NiceMock<MockNotifierObserver> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AllDownloadItemNotifierTest);
+};
+
+} // namespace
+
+TEST_F(AllDownloadItemNotifierTest, AllDownloadItemNotifierTest_0) {
+ content::DownloadManager::DownloadVector items;
+ items.push_back(&item());
+ EXPECT_CALL(manager(), GetAllDownloads(_)).WillOnce(SetArgPointee<0>(items));
+ SetNotifier();
+
+ EXPECT_CALL(observer(), OnDownloadUpdated(&manager(), &item()));
+ NotifierAsItemObserver()->OnDownloadUpdated(&item());
+
+ EXPECT_CALL(observer(), OnDownloadOpened(&manager(), &item()));
+ NotifierAsItemObserver()->OnDownloadOpened(&item());
+
+ EXPECT_CALL(observer(), OnDownloadRemoved(&manager(), &item()));
+ NotifierAsItemObserver()->OnDownloadRemoved(&item());
+
+ EXPECT_CALL(manager(), RemoveObserver(NotifierAsManagerObserver()));
+ ClearNotifier();
+}
+
+TEST_F(AllDownloadItemNotifierTest, AllDownloadItemNotifierTest_1) {
+ EXPECT_CALL(manager(), GetAllDownloads(_));
+ SetNotifier();
+
+ EXPECT_CALL(observer(), OnDownloadCreated(&manager(), &item()));
+ NotifierAsManagerObserver()->OnDownloadCreated(&manager(), &item());
+
+ EXPECT_CALL(manager(), RemoveObserver(NotifierAsManagerObserver()));
+ NotifierAsManagerObserver()->ManagerGoingDown(&manager());
+
+ ClearNotifier();
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/BUILD.gn b/chromium/components/download/internal/BUILD.gn
index d1e1e43b885..f138251c6fc 100644
--- a/chromium/components/download/internal/BUILD.gn
+++ b/chromium/components/download/internal/BUILD.gn
@@ -10,36 +10,59 @@ if (is_android) {
static_library("internal") {
visibility = [
":*",
- "//components/download",
- "//components/download/content",
+ "//components/download/content/factory",
+ "//components/download/content/internal",
"//components/download/internal/test:test_support",
]
sources = [
+ "client_set.cc",
+ "client_set.h",
"config.cc",
"config.h",
+ "controller.h",
+ "controller_impl.cc",
+ "controller_impl.h",
"download_driver.h",
"download_service_impl.cc",
"download_service_impl.h",
+ "download_store.cc",
+ "download_store.h",
"driver_entry.cc",
"driver_entry.h",
"entry.cc",
"entry.h",
+ "entry_utils.cc",
+ "entry_utils.h",
+ "file_monitor.h",
+ "file_monitor_impl.cc",
+ "file_monitor_impl.h",
"model.h",
"model_impl.cc",
"model_impl.h",
- "noop_store.cc",
- "noop_store.h",
- "scheduler/battery_listener.cc",
- "scheduler/battery_listener.h",
- "scheduler/network_listener.cc",
- "scheduler/network_listener.h",
+ "proto_conversions.cc",
+ "proto_conversions.h",
+ "scheduler/device_status.cc",
+ "scheduler/device_status.h",
+ "scheduler/device_status_listener.cc",
+ "scheduler/device_status_listener.h",
+ "scheduler/scheduler.h",
+ "scheduler/scheduler_impl.cc",
+ "scheduler/scheduler_impl.h",
+ "service_config_impl.cc",
+ "service_config_impl.h",
+ "startup_status.cc",
+ "startup_status.h",
+ "stats.cc",
+ "stats.h",
"store.h",
]
deps = [
"//base",
+ "//components/download/internal/proto",
"//components/download/public",
+ "//components/leveldb_proto",
"//net",
]
}
@@ -47,18 +70,26 @@ static_library("internal") {
source_set("unit_tests") {
testonly = true
- visibility = [ "//components/download:unit_tests" ]
-
sources = [
+ "client_set_unittest.cc",
+ "controller_impl_unittest.cc",
+ "download_service_impl_unittest.cc",
+ "download_store_unittest.cc",
+ "entry_utils_unittest.cc",
+ "file_monitor_unittest.cc",
"model_impl_unittest.cc",
- "scheduler/battery_listener_unittest.cc",
- "scheduler/network_listener_unittest.cc",
+ "proto_conversions_unittest.cc",
+ "scheduler/device_status_listener_unittest.cc",
+ "scheduler/scheduler_impl_unittest.cc",
+ "service_config_impl_unittest.cc",
]
deps = [
":internal",
"//base/test:test_support",
+ "//components/download/internal/proto",
"//components/download/internal/test:test_support",
+ "//components/leveldb_proto:test_support",
"//testing/gmock",
"//testing/gtest",
]
diff --git a/chromium/components/download/internal/DEPS b/chromium/components/download/internal/DEPS
index 4ca86d9b1fe..6a99578f611 100644
--- a/chromium/components/download/internal/DEPS
+++ b/chromium/components/download/internal/DEPS
@@ -1,6 +1,7 @@
include_rules = [
"-components/download/content",
"-content",
+ "+components/leveldb_proto",
"+base",
"+net",
]
diff --git a/chromium/components/download/internal/client_set.cc b/chromium/components/download/internal/client_set.cc
new file mode 100644
index 00000000000..f74834b0d9a
--- /dev/null
+++ b/chromium/components/download/internal/client_set.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/client_set.h"
+
+namespace download {
+
+ClientSet::ClientSet(std::unique_ptr<DownloadClientMap> clients)
+ : clients_(std::move(clients)) {
+ DCHECK(clients_->find(DownloadClient::INVALID) == clients_->end());
+}
+
+ClientSet::~ClientSet() = default;
+
+std::set<DownloadClient> ClientSet::GetRegisteredClients() const {
+ std::set<DownloadClient> clients;
+ for (const auto& it : *clients_) {
+ clients.insert(it.first);
+ }
+
+ return clients;
+}
+
+Client* ClientSet::GetClient(DownloadClient id) const {
+ const auto& it = clients_->find(id);
+ return it == clients_->end() ? nullptr : it->second.get();
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/client_set.h b/chromium/components/download/internal/client_set.h
new file mode 100644
index 00000000000..2daa5206782
--- /dev/null
+++ b/chromium/components/download/internal/client_set.h
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_CLIENT_SET_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_CLIENT_SET_H_
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/macros.h"
+#include "components/download/public/clients.h"
+
+namespace download {
+
+// Helper class to hold a list of Clients and associates them with their
+// respective DownloadClient identifier.
+class ClientSet {
+ public:
+ explicit ClientSet(std::unique_ptr<DownloadClientMap> clients);
+ virtual ~ClientSet();
+
+ std::set<DownloadClient> GetRegisteredClients() const;
+ Client* GetClient(DownloadClient id) const;
+
+ private:
+ std::unique_ptr<DownloadClientMap> clients_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSet);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_CLIENT_SET_H_
diff --git a/chromium/components/download/internal/client_set_unittest.cc b/chromium/components/download/internal/client_set_unittest.cc
new file mode 100644
index 00000000000..04d454c72e3
--- /dev/null
+++ b/chromium/components/download/internal/client_set_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/client_set.h"
+
+#include <algorithm>
+
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/test/empty_client.h"
+#include "components/download/public/clients.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace download {
+
+TEST(DownloadServiceClientSetTest, TestGetClient) {
+ auto client = base::MakeUnique<test::EmptyClient>();
+ Client* raw_client = client.get();
+
+ auto client_map = base::MakeUnique<DownloadClientMap>();
+ client_map->insert(std::make_pair(DownloadClient::TEST, std::move(client)));
+ ClientSet clients(std::move(client_map));
+
+ EXPECT_EQ(raw_client, clients.GetClient(DownloadClient::TEST));
+ EXPECT_EQ(nullptr, clients.GetClient(DownloadClient::INVALID));
+}
+
+TEST(DownloadServiceClientSetTest, TestGetRegisteredClients) {
+ auto client = base::MakeUnique<test::EmptyClient>();
+
+ auto client_map = base::MakeUnique<DownloadClientMap>();
+ client_map->insert(std::make_pair(DownloadClient::TEST, std::move(client)));
+ ClientSet clients(std::move(client_map));
+
+ std::set<DownloadClient> expected_set = {DownloadClient::TEST};
+ std::set<DownloadClient> actual_set = clients.GetRegisteredClients();
+
+ EXPECT_EQ(expected_set.size(), actual_set.size());
+ EXPECT_TRUE(
+ std::equal(expected_set.begin(), expected_set.end(), actual_set.begin()));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/config.cc b/chromium/components/download/internal/config.cc
index 564da91b881..02bcfdde4ef 100644
--- a/chromium/components/download/internal/config.cc
+++ b/chromium/components/download/internal/config.cc
@@ -16,29 +16,50 @@ namespace download {
namespace {
// Default value for max concurrent downloads configuration.
-const int kDefaultMaxConcurrentDownloads = 4;
+const uint32_t kDefaultMaxConcurrentDownloads = 4;
// Default value for maximum running downloads of the download service.
-const int kDefaultMaxRunningDownloads = 1;
+const uint32_t kDefaultMaxRunningDownloads = 2;
// Default value for maximum scheduled downloads.
-const int kDefaultMaxScheduledDownloads = 15;
+const uint32_t kDefaultMaxScheduledDownloads = 15;
// Default value for maximum retry count.
-const int kDefaultMaxRetryCount = 5;
+const uint32_t kDefaultMaxRetryCount = 5;
// Default value for file keep alive time in minutes, keep the file alive for
// 12 hours by default.
-const int kDefaultFileKeepAliveTimeMinutes = 12 * 60;
+const base::TimeDelta kDefaultFileKeepAliveTime =
+ base::TimeDelta::FromHours(12);
+
+// Default value for file cleanup window in minutes, the system will schedule a
+// cleanup task within this window.
+const base::TimeDelta kDefaultFileCleanupWindow =
+ base::TimeDelta::FromHours(24);
+
+// Default value for the start window time for OS to schedule background task.
+const base::TimeDelta kDefaultWindowStartTime = base::TimeDelta::FromMinutes(5);
+
+// Default value for the end window time for OS to schedule background task.
+const base::TimeDelta kDefaultWindowEndTime = base::TimeDelta::FromHours(8);
+
+// The default delay to notify the observer when network changes from
+// disconnected to connected.
+const base::TimeDelta kDefaultNetworkChangeDelay =
+ base::TimeDelta::FromSeconds(5);
+
+// The default value of download retry delay when the download is failed.
+const base::TimeDelta kDefaultDownloadRetryDelay =
+ base::TimeDelta::FromSeconds(20);
// Helper routine to get Finch experiment parameter. If no Finch seed was found,
// use the |default_value|. The |name| should match an experiment
// parameter in Finch server configuration.
-int GetFinchConfigInt(const std::string& name, int default_value) {
+uint32_t GetFinchConfigUInt(const std::string& name, uint32_t default_value) {
std::string finch_value =
base::GetFieldTrialParamValueByFeature(kDownloadServiceFeature, name);
- int result;
- return base::StringToInt(finch_value, &result) ? result : default_value;
+ uint32_t result;
+ return base::StringToUint(finch_value, &result) ? result : default_value;
}
} // namespace
@@ -46,16 +67,36 @@ int GetFinchConfigInt(const std::string& name, int default_value) {
// static
std::unique_ptr<Configuration> Configuration::CreateFromFinch() {
std::unique_ptr<Configuration> config(new Configuration());
- config->max_concurrent_downloads = GetFinchConfigInt(
+ config->max_concurrent_downloads = GetFinchConfigUInt(
kMaxConcurrentDownloadsConfig, kDefaultMaxConcurrentDownloads);
- config->max_running_downloads = GetFinchConfigInt(
+ config->max_running_downloads = GetFinchConfigUInt(
kMaxRunningDownloadsConfig, kDefaultMaxRunningDownloads);
- config->max_scheduled_downloads = GetFinchConfigInt(
+ config->max_scheduled_downloads = GetFinchConfigUInt(
kMaxScheduledDownloadsConfig, kDefaultMaxScheduledDownloads);
config->max_retry_count =
- GetFinchConfigInt(kMaxRetryCountConfig, kDefaultMaxRetryCount);
- config->file_keep_alive_time = base::TimeDelta::FromMinutes(GetFinchConfigInt(
- kFileKeepAliveTimeMinutesConfig, kDefaultFileKeepAliveTimeMinutes));
+ GetFinchConfigUInt(kMaxRetryCountConfig, kDefaultMaxRetryCount);
+ config->file_keep_alive_time =
+ base::TimeDelta::FromMinutes(base::saturated_cast<int>(
+ GetFinchConfigUInt(kFileKeepAliveTimeMinutesConfig,
+ kDefaultFileKeepAliveTime.InMinutes())));
+ config->file_cleanup_window =
+ base::TimeDelta::FromMinutes(base::saturated_cast<int>(
+ GetFinchConfigUInt(kFileCleanupWindowMinutesConfig,
+ kDefaultFileCleanupWindow.InMinutes())));
+ config->window_start_time =
+ base::TimeDelta::FromSeconds(base::saturated_cast<int>(GetFinchConfigUInt(
+ kWindowStartTimeSecondsConfig, kDefaultWindowStartTime.InSeconds())));
+ config->window_end_time =
+ base::TimeDelta::FromSeconds(base::saturated_cast<int>(GetFinchConfigUInt(
+ kWindowEndTimeSecondsConfig, kDefaultWindowEndTime.InSeconds())));
+ config->network_change_delay =
+ base::TimeDelta::FromMilliseconds(base::saturated_cast<int>(
+ GetFinchConfigUInt(kNetworkChangeDelayMsConfig,
+ kDefaultNetworkChangeDelay.InMilliseconds())));
+ config->download_retry_delay =
+ base::TimeDelta::FromMilliseconds(base::saturated_cast<int>(
+ GetFinchConfigUInt(kDownloadRetryDelayMsConfig,
+ kDefaultDownloadRetryDelay.InMilliseconds())));
return config;
}
@@ -64,7 +105,11 @@ Configuration::Configuration()
max_running_downloads(kDefaultMaxRunningDownloads),
max_scheduled_downloads(kDefaultMaxScheduledDownloads),
max_retry_count(kDefaultMaxRetryCount),
- file_keep_alive_time(
- base::TimeDelta::FromMinutes(kDefaultFileKeepAliveTimeMinutes)) {}
+ file_keep_alive_time(kDefaultFileKeepAliveTime),
+ file_cleanup_window(kDefaultFileCleanupWindow),
+ window_start_time(kDefaultWindowStartTime),
+ window_end_time(kDefaultWindowEndTime),
+ network_change_delay(kDefaultNetworkChangeDelay),
+ download_retry_delay(kDefaultDownloadRetryDelay) {}
} // namespace download
diff --git a/chromium/components/download/internal/config.h b/chromium/components/download/internal/config.h
index c5b3b0d44d3..073676fcc38 100644
--- a/chromium/components/download/internal/config.h
+++ b/chromium/components/download/internal/config.h
@@ -27,6 +27,23 @@ constexpr char kMaxRetryCountConfig[] = "max_retry_count";
// Configuration name for file keep alive time.
constexpr char kFileKeepAliveTimeMinutesConfig[] = "file_keep_alive_time";
+// Configuration name for file keep alive time.
+constexpr char kFileCleanupWindowMinutesConfig[] = "file_cleanup_window";
+
+// Configuration name for window start time.
+constexpr char kWindowStartTimeSecondsConfig[] = "window_start_time_seconds";
+
+// Configuration name for window end time.
+constexpr char kWindowEndTimeSecondsConfig[] = "window_end_time_seconds";
+
+// Configuration name for the delay to notify network status change, measured in
+// milliseconds.
+constexpr char kNetworkChangeDelayMsConfig[] = "network_change_delay_ms";
+
+// Configuration name for the retry delay when the download is failed, measured
+// in milliseconds.
+constexpr char kDownloadRetryDelayMsConfig[] = "retry_delay_ms";
+
// Download service configuration.
//
// Loaded based on experiment parameters from the server. Use default values if
@@ -39,23 +56,41 @@ struct Configuration {
// The maximum number of downloads the DownloadService can have currently in
// Active or Paused states.
- int max_concurrent_downloads;
+ uint32_t max_concurrent_downloads;
// The maximum number of downloads the DownloadService can have currently in
// only Active state.
- int max_running_downloads;
+ uint32_t max_running_downloads;
- // The maximum number of downloads that are scheduled but not yet in Active
- // state, for each client using the download service.
- int max_scheduled_downloads;
+ // The maximum number of downloads that are scheduled for each client using
+ // the download service.
+ uint32_t max_scheduled_downloads;
// The maximum number of retries before the download is aborted.
- int max_retry_count;
+ uint32_t max_retry_count;
// The time that the download service will keep the files around before
// deleting them if the client hasn't handle the files.
base::TimeDelta file_keep_alive_time;
+ // The length of the flexible time window during which the scheduler must
+ // schedule a file cleanup task.
+ base::TimeDelta file_cleanup_window;
+
+ // The start window time in seconds for OS to schedule background task.
+ // The OS will trigger the background task in this window.
+ base::TimeDelta window_start_time;
+
+ // The end window time in seconds for OS to schedule background task.
+ // The OS will trigger the background task in this window.
+ base::TimeDelta window_end_time;
+
+ // The delay to notify network status changes.
+ base::TimeDelta network_change_delay;
+
+ // The delay to retry a download when the download is failed.
+ base::TimeDelta download_retry_delay;
+
private:
DISALLOW_COPY_AND_ASSIGN(Configuration);
};
diff --git a/chromium/components/download/internal/controller.h b/chromium/components/download/internal/controller.h
new file mode 100644
index 00000000000..a19a223f0f5
--- /dev/null
+++ b/chromium/components/download/internal/controller.h
@@ -0,0 +1,110 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/download/public/clients.h"
+#include "components/download/public/download_service.h"
+#include "components/download/public/download_task_types.h"
+
+namespace download {
+
+struct DownloadParams;
+struct SchedulingParams;
+
+// The type of completion when the download entry transits to complete state.
+// TODO(xingliu): Implement timeout and unknown failure types.
+enum class CompletionType {
+ // The download is successfully finished.
+ SUCCEED = 0,
+ // The download is interrupted and failed.
+ FAIL = 1,
+ // The download is aborted by the client.
+ ABORT = 2,
+ // The download is timed out and the connection is closed.
+ TIMEOUT = 3,
+ // The download is failed for unknown reasons.
+ UNKNOWN = 4,
+ // The download is cancelled by the client.
+ CANCEL = 5,
+ // The count of entries for the enum.
+ COUNT = 6,
+};
+
+// The core Controller responsible for gluing various DownloadService components
+// together to manage the active downloads.
+class Controller {
+ public:
+ enum class State {
+ // The Controller has been created but has not been initialized yet. It
+ // cannot be used.
+ CREATED = 1,
+
+ // The Controller has been created and Initialize() has been called but has
+ // not yet finished. It cannot be used.
+ INITIALIZING = 2,
+
+ // The Controller has been created and initialized. It can be used.
+ READY = 3,
+
+ // The Controller failed to initialize and is in the process of recovering.
+ // It cannot be used.
+ RECOVERING = 4,
+
+ // The Controller was unable to recover and is unusable this session.
+ UNAVAILABLE = 5,
+ };
+
+ Controller() = default;
+ virtual ~Controller() = default;
+
+ // Initializes the controller. Initialization may be asynchronous.
+ virtual void Initialize(const base::Closure& callback) = 0;
+
+ // Returns the status of Controller.
+ virtual State GetState() = 0;
+
+ // Starts a download with |params|. See DownloadParams::StartCallback and
+ // DownloadParams::StartResponse for information on how a caller can determine
+ // whether or not the download was successfully accepted and queued.
+ virtual void StartDownload(const DownloadParams& params) = 0;
+
+ // Pauses a download request associated with |guid| if one exists.
+ virtual void PauseDownload(const std::string& guid) = 0;
+
+ // Resumes a download request associated with |guid| if one exists. The
+ // download request may or may not start downloading at this time, but it will
+ // no longer be blocked by any prior PauseDownload() actions.
+ virtual void ResumeDownload(const std::string& guid) = 0;
+
+ // Cancels a download request associated with |guid| if one exists.
+ virtual void CancelDownload(const std::string& guid) = 0;
+
+ // Changes the SchedulingParams of a download request associated with |guid|
+ // to |params|.
+ virtual void ChangeDownloadCriteria(const std::string& guid,
+ const SchedulingParams& params) = 0;
+
+ // Exposes the owner of the download request for |guid| if one exists.
+ // Otherwise returns DownloadClient::INVALID for an unowned entry.
+ virtual DownloadClient GetOwnerOfDownload(const std::string& guid) = 0;
+
+ // See DownloadService::OnStartScheduledTask.
+ virtual void OnStartScheduledTask(DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) = 0;
+
+ // See DownloadService::OnStopScheduledTask.
+ virtual bool OnStopScheduledTask(DownloadTaskType task_type) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Controller);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_H_
diff --git a/chromium/components/download/internal/controller_impl.cc b/chromium/components/download/internal/controller_impl.cc
new file mode 100644
index 00000000000..03fa70c46ec
--- /dev/null
+++ b/chromium/components/download/internal/controller_impl.cc
@@ -0,0 +1,1034 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/controller_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/internal/client_set.h"
+#include "components/download/internal/config.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/entry_utils.h"
+#include "components/download/internal/file_monitor.h"
+#include "components/download/internal/model.h"
+#include "components/download/internal/scheduler/scheduler.h"
+#include "components/download/internal/stats.h"
+#include "components/download/public/client.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace download {
+namespace {
+
+// Helper function to transit the state of |entry| to |new_state|.
+void TransitTo(Entry* entry, Entry::State new_state, Model* model) {
+ DCHECK(entry);
+ if (entry->state == new_state)
+ return;
+ entry->state = new_state;
+ model->Update(*entry);
+}
+
+// Helper function to move from a CompletionType to a Client::FailureReason.
+Client::FailureReason FailureReasonFromCompletionType(CompletionType type) {
+ // SUCCEED does not map to a FailureReason.
+ DCHECK_NE(CompletionType::SUCCEED, type);
+
+ switch (type) {
+ case CompletionType::FAIL:
+ return Client::FailureReason::NETWORK;
+ case CompletionType::ABORT:
+ return Client::FailureReason::ABORTED;
+ case CompletionType::TIMEOUT:
+ return Client::FailureReason::TIMEDOUT;
+ case CompletionType::UNKNOWN:
+ return Client::FailureReason::UNKNOWN;
+ case CompletionType::CANCEL:
+ return Client::FailureReason::CANCELLED;
+ default:
+ NOTREACHED();
+ }
+
+ return Client::FailureReason::UNKNOWN;
+}
+
+// Helper function to determine if more downloads can be activated based on
+// configuration.
+bool CanActivateMoreDownloads(Configuration* config,
+ uint32_t active_count,
+ uint32_t paused_count) {
+ if (config->max_concurrent_downloads <= paused_count + active_count ||
+ config->max_running_downloads <= active_count) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+ControllerImpl::ControllerImpl(
+ Configuration* config,
+ std::unique_ptr<ClientSet> clients,
+ std::unique_ptr<DownloadDriver> driver,
+ std::unique_ptr<Model> model,
+ std::unique_ptr<DeviceStatusListener> device_status_listener,
+ std::unique_ptr<Scheduler> scheduler,
+ std::unique_ptr<TaskScheduler> task_scheduler,
+ std::unique_ptr<FileMonitor> file_monitor,
+ const base::FilePath& download_file_dir)
+ : config_(config),
+ download_file_dir_(download_file_dir),
+ clients_(std::move(clients)),
+ driver_(std::move(driver)),
+ model_(std::move(model)),
+ device_status_listener_(std::move(device_status_listener)),
+ scheduler_(std::move(scheduler)),
+ task_scheduler_(std::move(task_scheduler)),
+ file_monitor_(std::move(file_monitor)),
+ controller_state_(State::CREATED),
+ weak_ptr_factory_(this) {}
+
+ControllerImpl::~ControllerImpl() = default;
+
+void ControllerImpl::Initialize(const base::Closure& callback) {
+ DCHECK_EQ(controller_state_, State::CREATED);
+
+ init_callback_ = callback;
+ controller_state_ = State::INITIALIZING;
+
+ driver_->Initialize(this);
+ model_->Initialize(this);
+ file_monitor_->Initialize(base::Bind(&ControllerImpl::OnFileMonitorReady,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+Controller::State ControllerImpl::GetState() {
+ return controller_state_;
+}
+
+void ControllerImpl::StartDownload(const DownloadParams& params) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+
+ // TODO(dtrainor): Validate all input parameters.
+ DCHECK_LE(base::Time::Now(), params.scheduling_params.cancel_time);
+
+ if (controller_state_ != State::READY) {
+ HandleStartDownloadResponse(params.client, params.guid,
+ DownloadParams::StartResult::INTERNAL_ERROR,
+ params.callback);
+ return;
+ }
+
+ KillTimedOutDownloads();
+
+ if (start_callbacks_.find(params.guid) != start_callbacks_.end() ||
+ model_->Get(params.guid) != nullptr) {
+ HandleStartDownloadResponse(params.client, params.guid,
+ DownloadParams::StartResult::UNEXPECTED_GUID,
+ params.callback);
+ return;
+ }
+
+ auto* client = clients_->GetClient(params.client);
+ if (!client) {
+ HandleStartDownloadResponse(params.client, params.guid,
+ DownloadParams::StartResult::UNEXPECTED_CLIENT,
+ params.callback);
+ return;
+ }
+
+ uint32_t client_count =
+ util::GetNumberOfEntriesForClient(params.client, model_->PeekEntries());
+ if (client_count >= config_->max_scheduled_downloads) {
+ HandleStartDownloadResponse(params.client, params.guid,
+ DownloadParams::StartResult::BACKOFF,
+ params.callback);
+ return;
+ }
+
+ start_callbacks_[params.guid] = params.callback;
+ Entry entry(params);
+ entry.target_file_path = download_file_dir_.AppendASCII(params.guid);
+ model_->Add(entry);
+}
+
+void ControllerImpl::PauseDownload(const std::string& guid) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+ if (controller_state_ != State::READY)
+ return;
+
+ auto* entry = model_->Get(guid);
+
+ if (!entry || entry->state == Entry::State::PAUSED ||
+ entry->state == Entry::State::COMPLETE ||
+ entry->state == Entry::State::NEW) {
+ return;
+ }
+
+ TransitTo(entry, Entry::State::PAUSED, model_.get());
+ UpdateDriverState(entry);
+
+ // Pausing a download may yield a concurrent slot to start a new download, and
+ // may change the scheduling criteria.
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::ResumeDownload(const std::string& guid) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+ if (controller_state_ != State::READY)
+ return;
+
+ auto* entry = model_->Get(guid);
+ DCHECK(entry);
+
+ if (entry->state != Entry::State::PAUSED)
+ return;
+
+ TransitTo(entry, Entry::State::ACTIVE, model_.get());
+ UpdateDriverState(entry);
+
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::CancelDownload(const std::string& guid) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+ if (controller_state_ != State::READY)
+ return;
+
+ auto* entry = model_->Get(guid);
+ if (!entry)
+ return;
+
+ if (entry->state == Entry::State::NEW) {
+ // Check if we're currently trying to add the download.
+ DCHECK(start_callbacks_.find(entry->guid) != start_callbacks_.end());
+ HandleStartDownloadResponse(entry->client, guid,
+ DownloadParams::StartResult::CLIENT_CANCELLED);
+ return;
+ }
+
+ HandleCompleteDownload(CompletionType::CANCEL, guid);
+}
+
+void ControllerImpl::ChangeDownloadCriteria(const std::string& guid,
+ const SchedulingParams& params) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+ if (controller_state_ != State::READY)
+ return;
+
+ auto* entry = model_->Get(guid);
+ if (!entry || entry->scheduling_params == params) {
+ DVLOG(1) << "Try to update the same scheduling parameters.";
+ return;
+ }
+
+ UpdateDriverState(entry);
+
+ // Update the scheduling parameters.
+ entry->scheduling_params = params;
+ model_->Update(*entry);
+
+ ActivateMoreDownloads();
+}
+
+DownloadClient ControllerImpl::GetOwnerOfDownload(const std::string& guid) {
+ DCHECK(controller_state_ == State::READY ||
+ controller_state_ == State::UNAVAILABLE);
+ if (controller_state_ != State::READY)
+ return DownloadClient::INVALID;
+
+ auto* entry = model_->Get(guid);
+ return entry ? entry->client : DownloadClient::INVALID;
+}
+
+void ControllerImpl::OnStartScheduledTask(
+ DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) {
+ task_finished_callbacks_[task_type] = callback;
+
+ switch (controller_state_) {
+ case State::READY:
+ if (task_type == DownloadTaskType::DOWNLOAD_TASK) {
+ ActivateMoreDownloads();
+ } else if (task_type == DownloadTaskType::CLEANUP_TASK) {
+ RemoveCleanupEligibleDownloads();
+ ScheduleCleanupTask();
+ }
+ break;
+ case State::UNAVAILABLE:
+ HandleTaskFinished(task_type, false,
+ stats::ScheduledTaskStatus::ABORTED_ON_FAILED_INIT);
+ break;
+ case State::CREATED: // Intentional fallthrough.
+ case State::INITIALIZING: // Intentional fallthrough.
+ case State::RECOVERING: // Intentional fallthrough.
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool ControllerImpl::OnStopScheduledTask(DownloadTaskType task_type) {
+ HandleTaskFinished(task_type, false,
+ stats::ScheduledTaskStatus::CANCELLED_ON_STOP);
+ return true;
+}
+
+void ControllerImpl::OnCompleteCleanupTask() {
+ HandleTaskFinished(DownloadTaskType::CLEANUP_TASK, false,
+ stats::ScheduledTaskStatus::COMPLETED_NORMALLY);
+}
+
+void ControllerImpl::RemoveCleanupEligibleDownloads() {
+ auto timed_out_entries = file_monitor_->CleanupFilesForCompletedEntries(
+ model_->PeekEntries(), base::Bind(&ControllerImpl::OnCompleteCleanupTask,
+ weak_ptr_factory_.GetWeakPtr()));
+ for (auto* entry : timed_out_entries) {
+ DCHECK_EQ(Entry::State::COMPLETE, entry->state);
+ model_->Remove(entry->guid);
+ }
+}
+
+void ControllerImpl::HandleTaskFinished(DownloadTaskType task_type,
+ bool needs_reschedule,
+ stats::ScheduledTaskStatus status) {
+ if (task_finished_callbacks_.find(task_type) ==
+ task_finished_callbacks_.end()) {
+ return;
+ }
+
+ if (status != stats::ScheduledTaskStatus::CANCELLED_ON_STOP) {
+ base::ResetAndReturn(&task_finished_callbacks_[task_type])
+ .Run(needs_reschedule);
+ }
+ // TODO(dtrainor): It might be useful to log how many downloads we have
+ // running when we're asked to stop processing.
+ stats::LogScheduledTaskStatus(task_type, status);
+ task_finished_callbacks_.erase(task_type);
+}
+
+void ControllerImpl::OnDriverReady(bool success) {
+ DCHECK(!startup_status_.driver_ok.has_value());
+ startup_status_.driver_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnDriverHardRecoverComplete(bool success) {
+ DCHECK(!startup_status_.driver_ok.has_value());
+ startup_status_.driver_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnDownloadCreated(const DriverEntry& download) {
+ if (controller_state_ != State::READY)
+ return;
+
+ Entry* entry = model_->Get(download.guid);
+
+ if (!entry) {
+ HandleExternalDownload(download.guid, true);
+ return;
+ }
+
+ download::Client* client = clients_->GetClient(entry->client);
+ DCHECK(client);
+ using ShouldDownload = download::Client::ShouldDownload;
+ ShouldDownload should_download = client->OnDownloadStarted(
+ download.guid, download.url_chain, download.response_headers);
+ stats::LogStartDownloadResponse(entry->client, should_download);
+ if (should_download == ShouldDownload::ABORT) {
+ HandleCompleteDownload(CompletionType::ABORT, entry->guid);
+ }
+}
+
+void ControllerImpl::OnDownloadFailed(const DriverEntry& download,
+ FailureType failure_type) {
+ if (controller_state_ != State::READY)
+ return;
+
+ Entry* entry = model_->Get(download.guid);
+ if (!entry) {
+ HandleExternalDownload(download.guid, false);
+ return;
+ }
+
+ if (failure_type == FailureType::RECOVERABLE) {
+ // Because the network offline signal comes later than actual download
+ // failure, retry the download after a delay to avoid the retry to fail
+ // immediately again.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ControllerImpl::UpdateDriverStateWithGuid,
+ weak_ptr_factory_.GetWeakPtr(), download.guid),
+ config_->download_retry_delay);
+ } else {
+ // TODO(dtrainor, xingliu): We probably have to prevent cancel calls from
+ // coming through here as we remove downloads (especially through
+ // initialization).
+ HandleCompleteDownload(CompletionType::FAIL, download.guid);
+ }
+}
+
+void ControllerImpl::OnDownloadSucceeded(const DriverEntry& download) {
+ if (controller_state_ != State::READY)
+ return;
+
+ Entry* entry = model_->Get(download.guid);
+ if (!entry) {
+ HandleExternalDownload(download.guid, false);
+ return;
+ }
+
+ HandleCompleteDownload(CompletionType::SUCCEED, download.guid);
+}
+
+void ControllerImpl::OnDownloadUpdated(const DriverEntry& download) {
+ if (controller_state_ != State::READY)
+ return;
+
+ Entry* entry = model_->Get(download.guid);
+ if (!entry) {
+ HandleExternalDownload(download.guid, !download.paused);
+ return;
+ }
+
+ DCHECK_EQ(download.state, DriverEntry::State::IN_PROGRESS);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ControllerImpl::SendOnDownloadUpdated,
+ weak_ptr_factory_.GetWeakPtr(), entry->client,
+ download.guid, download.bytes_downloaded));
+}
+
+void ControllerImpl::OnFileMonitorReady(bool success) {
+ DCHECK(!startup_status_.file_monitor_ok.has_value());
+ startup_status_.file_monitor_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnFileMonitorHardRecoverComplete(bool success) {
+ DCHECK(!startup_status_.file_monitor_ok.has_value());
+ startup_status_.file_monitor_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnModelReady(bool success) {
+ DCHECK(!startup_status_.model_ok.has_value());
+ startup_status_.model_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnModelHardRecoverComplete(bool success) {
+ DCHECK(!startup_status_.model_ok.has_value());
+ startup_status_.model_ok = success;
+ AttemptToFinalizeSetup();
+}
+
+void ControllerImpl::OnItemAdded(bool success,
+ DownloadClient client,
+ const std::string& guid) {
+ // If the StartCallback doesn't exist, we already notified the Client about
+ // this item. That means something went wrong, so stop here.
+ if (start_callbacks_.find(guid) == start_callbacks_.end())
+ return;
+
+ if (!success) {
+ HandleStartDownloadResponse(client, guid,
+ DownloadParams::StartResult::INTERNAL_ERROR);
+ return;
+ }
+
+ HandleStartDownloadResponse(client, guid,
+ DownloadParams::StartResult::ACCEPTED);
+
+ Entry* entry = model_->Get(guid);
+ DCHECK(entry);
+ DCHECK_EQ(Entry::State::NEW, entry->state);
+ TransitTo(entry, Entry::State::AVAILABLE, model_.get());
+
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::OnItemUpdated(bool success,
+ DownloadClient client,
+ const std::string& guid) {
+ Entry* entry = model_->Get(guid);
+ DCHECK(entry);
+
+ // Now that we're sure that our state is set correctly, it is OK to remove the
+ // DriverEntry. If we restart we'll see a COMPLETE state and handle it
+ // accordingly.
+ if (entry->state == Entry::State::COMPLETE)
+ driver_->Remove(guid);
+
+ // TODO(dtrainor): If failed, clean up any download state accordingly.
+}
+
+void ControllerImpl::OnItemRemoved(bool success,
+ DownloadClient client,
+ const std::string& guid) {
+ // TODO(dtrainor): If failed, clean up any download state accordingly.
+}
+
+void ControllerImpl::OnDeviceStatusChanged(const DeviceStatus& device_status) {
+ if (controller_state_ != State::READY)
+ return;
+
+ UpdateDriverStates();
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::AttemptToFinalizeSetup() {
+ DCHECK(controller_state_ == State::INITIALIZING ||
+ controller_state_ == State::RECOVERING);
+
+ if (!startup_status_.Complete())
+ return;
+
+ bool in_recovery = controller_state_ == State::RECOVERING;
+
+ stats::LogControllerStartupStatus(in_recovery, startup_status_);
+ if (!startup_status_.Ok()) {
+ if (in_recovery) {
+ HandleUnrecoverableSetup();
+ NotifyServiceOfStartup();
+ } else {
+ StartHardRecoveryAttempt();
+ }
+
+ return;
+ }
+
+ device_status_listener_->Start(this);
+ PollActiveDriverDownloads();
+ CancelOrphanedRequests();
+ CleanupUnknownFiles();
+ RemoveCleanupEligibleDownloads();
+ ResolveInitialRequestStates();
+
+ NotifyClientsOfStartup(in_recovery);
+
+ controller_state_ = State::READY;
+
+ UpdateDriverStates();
+
+ KillTimedOutDownloads();
+ NotifyServiceOfStartup();
+
+ // Pull the initial straw if active downloads haven't reach maximum.
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::HandleUnrecoverableSetup() {
+ controller_state_ = State::UNAVAILABLE;
+
+ // If we cannot recover, notify Clients that the service is unavailable.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ControllerImpl::SendOnServiceUnavailable,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ControllerImpl::StartHardRecoveryAttempt() {
+ startup_status_.Reset();
+ controller_state_ = State::RECOVERING;
+
+ driver_->HardRecover();
+ model_->HardRecover();
+ file_monitor_->HardRecover(
+ base::Bind(&ControllerImpl::OnFileMonitorHardRecoverComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ControllerImpl::PollActiveDriverDownloads() {
+ DCHECK(controller_state_ == State::INITIALIZING ||
+ controller_state_ == State::RECOVERING);
+
+ std::set<std::string> guids = driver_->GetActiveDownloads();
+
+ for (auto guid : guids) {
+ if (!model_->Get(guid))
+ externally_active_downloads_.insert(guid);
+ }
+}
+
+void ControllerImpl::CancelOrphanedRequests() {
+ DCHECK(controller_state_ == State::INITIALIZING ||
+ controller_state_ == State::RECOVERING);
+
+ auto entries = model_->PeekEntries();
+
+ std::vector<std::string> guids_to_remove;
+ std::set<base::FilePath> files_to_remove;
+ for (auto* entry : entries) {
+ if (!clients_->GetClient(entry->client)) {
+ guids_to_remove.push_back(entry->guid);
+ files_to_remove.insert(entry->target_file_path);
+ }
+ }
+
+ for (const auto& guid : guids_to_remove) {
+ driver_->Remove(guid);
+ model_->Remove(guid);
+ }
+
+ file_monitor_->DeleteFiles(files_to_remove,
+ stats::FileCleanupReason::ORPHANED);
+}
+
+void ControllerImpl::CleanupUnknownFiles() {
+ DCHECK(controller_state_ == State::INITIALIZING ||
+ controller_state_ == State::RECOVERING);
+
+ auto entries = model_->PeekEntries();
+ std::vector<DriverEntry> driver_entries;
+ for (auto* entry : entries) {
+ base::Optional<DriverEntry> driver_entry = driver_->Find(entry->guid);
+ if (driver_entry.has_value())
+ driver_entries.push_back(driver_entry.value());
+ }
+
+ file_monitor_->DeleteUnknownFiles(entries, driver_entries);
+}
+
+void ControllerImpl::ResolveInitialRequestStates() {
+ DCHECK(controller_state_ == State::INITIALIZING ||
+ controller_state_ == State::RECOVERING);
+
+ auto entries = model_->PeekEntries();
+ for (auto* entry : entries) {
+ // Pull the initial Entry::State and DriverEntry::State.
+ Entry::State state = entry->state;
+ auto driver_entry = driver_->Find(entry->guid);
+ base::Optional<DriverEntry::State> driver_state;
+ if (driver_entry.has_value()) {
+ DCHECK_NE(DriverEntry::State::UNKNOWN, driver_entry->state);
+ driver_state = driver_entry->state;
+ }
+
+ // Determine what the new Entry::State should be based on the two original
+ // states of the two different systems.
+ Entry::State new_state = state;
+ switch (state) {
+ case Entry::State::NEW:
+ // This means we shut down but may have not ACK'ed the download. That
+ // is OK, we will still notify the Client about the GUID when we send
+ // them our initialize method.
+ new_state = Entry::State::AVAILABLE;
+ break;
+ case Entry::State::COMPLETE:
+ // We're already in our end state. Just stay here.
+ new_state = Entry::State::COMPLETE;
+ break;
+ case Entry::State::AVAILABLE: // Intentional fallthrough.
+ case Entry::State::ACTIVE: // Intentional fallthrough.
+ case Entry::State::PAUSED: {
+ // All three of these states are effectively driven by the DriverEntry
+ // state.
+ if (!driver_state.has_value()) {
+ // If we don't have a DriverEntry::State, just leave the state alone.
+ new_state = state;
+ break;
+ }
+
+ // If we have a real DriverEntry::State, we need to determine which of
+ // those states makes sense for our Entry. Our entry can either be in
+ // two states now: It's effective 'active' state (ACTIVE or PAUSED) or
+ // COMPLETE.
+ bool is_paused = state == Entry::State::PAUSED;
+ Entry::State active =
+ is_paused ? Entry::State::PAUSED : Entry::State::ACTIVE;
+
+ switch (driver_state.value()) {
+ case DriverEntry::State::IN_PROGRESS: // Intentional fallthrough.
+ case DriverEntry::State::INTERRUPTED:
+ // The DriverEntry isn't done, so we need to set the Entry to the
+ // 'active' state.
+ new_state = active;
+ break;
+ case DriverEntry::State::COMPLETE: // Intentional fallthrough.
+ // TODO(dtrainor, xingliu) Revisit this CANCELLED state to make sure
+ // all embedders behave properly.
+ case DriverEntry::State::CANCELLED:
+ // The DriverEntry is done. We need to set the Entry to the
+ // COMPLETE state.
+ new_state = Entry::State::COMPLETE;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Update the Entry::State to the new correct state.
+ if (new_state != entry->state) {
+ stats::LogRecoveryOperation(new_state);
+ TransitTo(entry, new_state, model_.get());
+ }
+
+ // Given the new correct state, update the DriverEntry to reflect the Entry.
+ switch (new_state) {
+ case Entry::State::NEW: // Intentional fallthrough.
+ case Entry::State::AVAILABLE: // Intentional fallthrough.
+ // We should not have a DriverEntry here.
+ if (driver_entry.has_value())
+ driver_->Remove(entry->guid);
+ break;
+ case Entry::State::ACTIVE: // Intentional fallthrough.
+ case Entry::State::PAUSED:
+ // We're in the correct state. Let UpdateDriverStates() restart us if
+ // it wants to.
+ break;
+ case Entry::State::COMPLETE:
+ if (state != Entry::State::COMPLETE) {
+ // We are changing states to COMPLETE. Handle this like a normal
+ // completed download.
+
+ // Treat CANCELLED and INTERRUPTED as failures. We have to assume the
+ // DriverEntry might not have persisted in time.
+ CompletionType completion_type =
+ (!driver_entry.has_value() ||
+ driver_entry->state == DriverEntry::State::CANCELLED ||
+ driver_entry->state == DriverEntry::State::INTERRUPTED)
+ ? CompletionType::UNKNOWN
+ : CompletionType::SUCCEED;
+ HandleCompleteDownload(completion_type, entry->guid);
+ } else {
+ // We're staying in COMPLETE. Make sure there is no DriverEntry here.
+ if (driver_entry.has_value())
+ driver_->Remove(entry->guid);
+ }
+ break;
+ case Entry::State::COUNT:
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
+void ControllerImpl::UpdateDriverStates() {
+ DCHECK(startup_status_.Complete());
+
+ for (auto* entry : model_->PeekEntries())
+ UpdateDriverState(entry);
+}
+
+void ControllerImpl::UpdateDriverStateWithGuid(const std::string& guid) {
+ Entry* entry = model_->Get(guid);
+ if (entry)
+ UpdateDriverState(entry);
+}
+
+void ControllerImpl::UpdateDriverState(Entry* entry) {
+ DCHECK_EQ(controller_state_, State::READY);
+
+ if (entry->state != Entry::State::ACTIVE &&
+ entry->state != Entry::State::PAUSED) {
+ return;
+ }
+
+ // This method will need to figure out what to do with a failed download and
+ // either a) restart it or b) fail the download.
+
+ base::Optional<DriverEntry> driver_entry = driver_->Find(entry->guid);
+
+ bool meets_device_criteria = device_status_listener_->CurrentDeviceStatus()
+ .MeetsCondition(entry->scheduling_params)
+ .MeetsRequirements();
+ bool force_pause =
+ !externally_active_downloads_.empty() &&
+ entry->scheduling_params.priority != SchedulingParams::Priority::UI;
+ bool entry_paused = entry->state == Entry::State::PAUSED;
+
+ bool pause_driver = entry_paused || force_pause || !meets_device_criteria;
+
+ if (pause_driver) {
+ if (driver_entry.has_value())
+ driver_->Pause(entry->guid);
+ } else {
+ bool is_new_attempt =
+ !driver_entry.has_value() ||
+ driver_entry->state == DriverEntry::State::INTERRUPTED;
+ if (is_new_attempt) {
+ entry->attempt_count++;
+ model_->Update(*entry);
+ if (entry->attempt_count >= config_->max_retry_count) {
+ HandleCompleteDownload(CompletionType::FAIL, entry->guid);
+ return;
+ }
+ }
+
+ if (driver_entry.has_value()) {
+ driver_->Resume(entry->guid);
+ } else {
+ driver_->Start(entry->request_params, entry->guid,
+ entry->target_file_path, NO_TRAFFIC_ANNOTATION_YET);
+ }
+ }
+}
+
+void ControllerImpl::NotifyClientsOfStartup(bool state_lost) {
+ std::set<Entry::State> ignored_states = {Entry::State::COMPLETE};
+ auto categorized = util::MapEntriesToClients(
+ clients_->GetRegisteredClients(), model_->PeekEntries(), ignored_states);
+
+ for (auto client_id : clients_->GetRegisteredClients()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ControllerImpl::SendOnServiceInitialized,
+ weak_ptr_factory_.GetWeakPtr(), client_id,
+ state_lost, categorized[client_id]));
+ }
+}
+
+void ControllerImpl::NotifyServiceOfStartup() {
+ if (init_callback_.is_null())
+ return;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::ResetAndReturn(&init_callback_));
+}
+
+void ControllerImpl::HandleStartDownloadResponse(
+ DownloadClient client,
+ const std::string& guid,
+ DownloadParams::StartResult result) {
+ auto callback = start_callbacks_[guid];
+ start_callbacks_.erase(guid);
+ HandleStartDownloadResponse(client, guid, result, callback);
+}
+
+void ControllerImpl::HandleStartDownloadResponse(
+ DownloadClient client,
+ const std::string& guid,
+ DownloadParams::StartResult result,
+ const DownloadParams::StartCallback& callback) {
+ stats::LogStartDownloadResult(client, result);
+
+ // UNEXPECTED_GUID means the guid was already in use. Don't remove this entry
+ // from the model because it's there due to another request.
+ if (result != DownloadParams::StartResult::ACCEPTED &&
+ result != DownloadParams::StartResult::UNEXPECTED_GUID &&
+ model_->Get(guid) != nullptr) {
+ model_->Remove(guid);
+ }
+
+ if (callback.is_null())
+ return;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, guid, result));
+}
+
+void ControllerImpl::HandleCompleteDownload(CompletionType type,
+ const std::string& guid) {
+ Entry* entry = model_->Get(guid);
+ DCHECK(entry);
+
+ if (entry->state == Entry::State::COMPLETE) {
+ DVLOG(1) << "Download is already completed.";
+ return;
+ }
+
+ auto driver_entry = driver_->Find(guid);
+ uint64_t file_size =
+ driver_entry.has_value() ? driver_entry->bytes_downloaded : 0;
+ stats::LogDownloadCompletion(
+ type, entry->completion_time - entry->create_time, file_size);
+
+ if (type == CompletionType::SUCCEED) {
+ DCHECK(driver_entry.has_value());
+ stats::LogFilePathRenamed(driver_entry->current_file_path !=
+ entry->target_file_path);
+ entry->target_file_path = driver_entry->current_file_path;
+
+ entry->completion_time = driver_entry->completion_time;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ControllerImpl::SendOnDownloadSucceeded,
+ weak_ptr_factory_.GetWeakPtr(), entry->client,
+ guid, driver_entry->current_file_path,
+ driver_entry->bytes_downloaded));
+ TransitTo(entry, Entry::State::COMPLETE, model_.get());
+ ScheduleCleanupTask();
+ } else {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ControllerImpl::SendOnDownloadFailed,
+ weak_ptr_factory_.GetWeakPtr(), entry->client,
+ guid, FailureReasonFromCompletionType(type)));
+ // TODO(dtrainor): Handle the case where we crash before the model write
+ // happens and we have no driver entry.
+ driver_->Remove(entry->guid);
+ model_->Remove(guid);
+ }
+
+ ActivateMoreDownloads();
+}
+
+void ControllerImpl::ScheduleCleanupTask() {
+ base::Time earliest_completion_time = base::Time::Max();
+ for (const Entry* entry : model_->PeekEntries()) {
+ if (entry->completion_time == base::Time() ||
+ entry->state != Entry::State::COMPLETE)
+ continue;
+ if (entry->completion_time < earliest_completion_time) {
+ earliest_completion_time = entry->completion_time;
+ }
+ }
+
+ if (earliest_completion_time == base::Time::Max())
+ return;
+
+ base::TimeDelta start_time = earliest_completion_time +
+ config_->file_keep_alive_time -
+ base::Time::Now();
+ base::TimeDelta end_time = start_time + config_->file_cleanup_window;
+
+ task_scheduler_->ScheduleTask(DownloadTaskType::CLEANUP_TASK, false, false,
+ std::ceil(start_time.InSecondsF()),
+ std::ceil(end_time.InSecondsF()));
+}
+
+void ControllerImpl::ScheduleKillDownloadTaskIfNecessary() {
+ base::Time earliest_cancel_time = base::Time::Max();
+ for (const Entry* entry : model_->PeekEntries()) {
+ if (entry->state != Entry::State::COMPLETE &&
+ entry->scheduling_params.cancel_time < earliest_cancel_time) {
+ earliest_cancel_time = entry->scheduling_params.cancel_time;
+ }
+ }
+
+ if (earliest_cancel_time == base::Time::Max())
+ return;
+
+ base::TimeDelta time_to_cancel =
+ earliest_cancel_time > base::Time::Now()
+ ? earliest_cancel_time - base::Time::Now()
+ : base::TimeDelta();
+
+ cancel_downloads_callback_.Reset(base::Bind(
+ &ControllerImpl::KillTimedOutDownloads, weak_ptr_factory_.GetWeakPtr()));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, cancel_downloads_callback_.callback(), time_to_cancel);
+}
+
+void ControllerImpl::KillTimedOutDownloads() {
+ for (const Entry* entry : model_->PeekEntries()) {
+ if (entry->state != Entry::State::COMPLETE &&
+ entry->scheduling_params.cancel_time <= base::Time::Now()) {
+ HandleCompleteDownload(CompletionType::TIMEOUT, entry->guid);
+ }
+ }
+
+ ScheduleKillDownloadTaskIfNecessary();
+}
+
+void ControllerImpl::ActivateMoreDownloads() {
+ if (controller_state_ != State::READY)
+ return;
+
+ // Check all the entries and the configuration to throttle number of
+ // downloads.
+ std::map<Entry::State, uint32_t> entries_states;
+ Model::EntryList scheduling_candidates;
+ for (auto* const entry : model_->PeekEntries()) {
+ entries_states[entry->state]++;
+ // Only schedule background tasks based on available and active entries.
+ if (entry->state == Entry::State::AVAILABLE ||
+ entry->state == Entry::State::ACTIVE) {
+ scheduling_candidates.emplace_back(entry);
+ }
+ }
+
+ uint32_t paused_count = entries_states[Entry::State::PAUSED];
+ uint32_t active_count = entries_states[Entry::State::ACTIVE];
+
+ bool has_actionable_downloads = false;
+ while (CanActivateMoreDownloads(config_, active_count, paused_count)) {
+ Entry* next = scheduler_->Next(
+ model_->PeekEntries(), device_status_listener_->CurrentDeviceStatus());
+ if (!next)
+ break;
+
+ has_actionable_downloads = true;
+ DCHECK_EQ(Entry::State::AVAILABLE, next->state);
+ TransitTo(next, Entry::State::ACTIVE, model_.get());
+ active_count++;
+ UpdateDriverState(next);
+ }
+
+ if (!has_actionable_downloads) {
+ HandleTaskFinished(DownloadTaskType::DOWNLOAD_TASK, false,
+ stats::ScheduledTaskStatus::COMPLETED_NORMALLY);
+ }
+
+ scheduler_->Reschedule(scheduling_candidates);
+}
+
+void ControllerImpl::HandleExternalDownload(const std::string& guid,
+ bool active) {
+ if (active) {
+ externally_active_downloads_.insert(guid);
+ } else {
+ externally_active_downloads_.erase(guid);
+ }
+
+ UpdateDriverStates();
+}
+
+void ControllerImpl::SendOnServiceInitialized(
+ DownloadClient client_id,
+ bool state_lost,
+ const std::vector<std::string>& guids) {
+ auto* client = clients_->GetClient(client_id);
+ DCHECK(client);
+ client->OnServiceInitialized(state_lost, guids);
+}
+
+void ControllerImpl::SendOnServiceUnavailable() {
+ for (auto client_id : clients_->GetRegisteredClients()) {
+ clients_->GetClient(client_id)->OnServiceUnavailable();
+ }
+}
+
+void ControllerImpl::SendOnDownloadUpdated(DownloadClient client_id,
+ const std::string& guid,
+ uint64_t bytes_downloaded) {
+ if (!model_->Get(guid))
+ return;
+
+ auto* client = clients_->GetClient(client_id);
+ DCHECK(client);
+ client->OnDownloadUpdated(guid, bytes_downloaded);
+}
+
+void ControllerImpl::SendOnDownloadSucceeded(DownloadClient client_id,
+ const std::string& guid,
+ const base::FilePath& path,
+ uint64_t size) {
+ auto* client = clients_->GetClient(client_id);
+ DCHECK(client);
+ client->OnDownloadSucceeded(guid, path, size);
+}
+
+void ControllerImpl::SendOnDownloadFailed(
+ DownloadClient client_id,
+ const std::string& guid,
+ download::Client::FailureReason reason) {
+ auto* client = clients_->GetClient(client_id);
+ DCHECK(client);
+ client->OnDownloadFailed(guid, reason);
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/controller_impl.h b/chromium/components/download/internal/controller_impl.h
new file mode 100644
index 00000000000..911ada78f00
--- /dev/null
+++ b/chromium/components/download/internal/controller_impl.h
@@ -0,0 +1,234 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_IMPL_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "components/download/internal/controller.h"
+#include "components/download/internal/download_driver.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/model.h"
+#include "components/download/internal/scheduler/device_status_listener.h"
+#include "components/download/internal/startup_status.h"
+#include "components/download/internal/stats.h"
+#include "components/download/public/client.h"
+#include "components/download/public/download_params.h"
+#include "components/download/public/task_scheduler.h"
+
+namespace download {
+
+class ClientSet;
+class DownloadDriver;
+class FileMonitor;
+class Model;
+class Scheduler;
+
+struct Configuration;
+struct SchedulingParams;
+
+// The internal Controller implementation. This class does all of the heavy
+// lifting for the DownloadService.
+class ControllerImpl : public Controller,
+ public DownloadDriver::Client,
+ public Model::Client,
+ public DeviceStatusListener::Observer {
+ public:
+ // |config| is externally owned and must be guaranteed to outlive this class.
+ ControllerImpl(Configuration* config,
+ std::unique_ptr<ClientSet> clients,
+ std::unique_ptr<DownloadDriver> driver,
+ std::unique_ptr<Model> model,
+ std::unique_ptr<DeviceStatusListener> device_status_listener,
+ std::unique_ptr<Scheduler> scheduler,
+ std::unique_ptr<TaskScheduler> task_scheduler,
+ std::unique_ptr<FileMonitor> file_monitor,
+ const base::FilePath& download_file_dir);
+ ~ControllerImpl() override;
+
+ // Controller implementation.
+ void Initialize(const base::Closure& callback) override;
+ State GetState() override;
+ void StartDownload(const DownloadParams& params) override;
+ void PauseDownload(const std::string& guid) override;
+ void ResumeDownload(const std::string& guid) override;
+ void CancelDownload(const std::string& guid) override;
+ void ChangeDownloadCriteria(const std::string& guid,
+ const SchedulingParams& params) override;
+ DownloadClient GetOwnerOfDownload(const std::string& guid) override;
+ void OnStartScheduledTask(DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) override;
+ bool OnStopScheduledTask(DownloadTaskType task_type) override;
+
+ private:
+ // DownloadDriver::Client implementation.
+ void OnDriverReady(bool success) override;
+ void OnDriverHardRecoverComplete(bool success) override;
+ void OnDownloadCreated(const DriverEntry& download) override;
+ void OnDownloadFailed(const DriverEntry& download,
+ FailureType failure_type) override;
+ void OnDownloadSucceeded(const DriverEntry& download) override;
+ void OnDownloadUpdated(const DriverEntry& download) override;
+
+ // Model::Client implementation.
+ void OnModelReady(bool success) override;
+ void OnModelHardRecoverComplete(bool success) override;
+ void OnItemAdded(bool success,
+ DownloadClient client,
+ const std::string& guid) override;
+ void OnItemUpdated(bool success,
+ DownloadClient client,
+ const std::string& guid) override;
+ void OnItemRemoved(bool success,
+ DownloadClient client,
+ const std::string& guid) override;
+
+ // Called when the file monitor and download file directory are initialized.
+ void OnFileMonitorReady(bool success);
+
+ // Called when the file monitor finishes attempting to recover itself.
+ void OnFileMonitorHardRecoverComplete(bool success);
+
+ // DeviceStatusListener::Observer implementation.
+ void OnDeviceStatusChanged(const DeviceStatus& device_status) override;
+
+ // Checks if initialization is complete and successful. If so, completes the
+ // internal state initialization.
+ void AttemptToFinalizeSetup();
+
+ // Called when setup and recovery failed. Shuts down the service and notifies
+ // the Clients.
+ void HandleUnrecoverableSetup();
+
+ // If initialization failed, try to reset the state of all components and
+ // restart them. If that attempt fails the service will be unavailable.
+ void StartHardRecoveryAttempt();
+
+ // Checks for all the currently active driver downloads. This lets us know
+ // which ones are active that we haven't tracked.
+ void PollActiveDriverDownloads();
+
+ // Cancels and cleans upany requests that are no longer associated with a
+ // Client in |clients_|.
+ void CancelOrphanedRequests();
+
+ // Cleans up any files that are left on the disk without any entries.
+ void CleanupUnknownFiles();
+
+ // Fixes any discrepancies in state between |model_| and |driver_|. Meant to
+ // resolve state issues during startup.
+ void ResolveInitialRequestStates();
+
+ // Updates the driver states based on the states of entries in download
+ // service.
+ void UpdateDriverStates();
+
+ // See |UpdateDriverState|.
+ void UpdateDriverStateWithGuid(const std::string& guid);
+
+ // Processes the download based on the state of |entry|. May start, pause
+ // or resume a download accordingly.
+ void UpdateDriverState(Entry* entry);
+
+ // Notifies all Client in |clients_| that this controller is initialized and
+ // lets them know which download requests we are aware of for their
+ // DownloadClient.
+ void NotifyClientsOfStartup(bool state_lost);
+
+ // Notifies the service that the startup has completed so that it can start
+ // processing any pending requests.
+ void NotifyServiceOfStartup();
+
+ void HandleStartDownloadResponse(DownloadClient client,
+ const std::string& guid,
+ DownloadParams::StartResult result);
+ void HandleStartDownloadResponse(
+ DownloadClient client,
+ const std::string& guid,
+ DownloadParams::StartResult result,
+ const DownloadParams::StartCallback& callback);
+
+ // Handles and clears any pending task finished callbacks.
+ void HandleTaskFinished(DownloadTaskType task_type,
+ bool needs_reschedule,
+ stats::ScheduledTaskStatus status);
+ void OnCompleteCleanupTask();
+
+ void HandleCompleteDownload(CompletionType type, const std::string& guid);
+
+ // Find more available entries to download, until the number of active entries
+ // reached maximum.
+ void ActivateMoreDownloads();
+
+ void RemoveCleanupEligibleDownloads();
+
+ void HandleExternalDownload(const std::string& guid, bool active);
+
+ // Postable methods meant to just be pass throughs to Client APIs. This is
+ // meant to help prevent reentrancy.
+ void SendOnServiceInitialized(DownloadClient client_id,
+ bool state_lost,
+ const std::vector<std::string>& guids);
+ void SendOnServiceUnavailable();
+ void SendOnDownloadUpdated(DownloadClient client_id,
+ const std::string& guid,
+ uint64_t bytes_downloaded);
+ void SendOnDownloadSucceeded(DownloadClient client_id,
+ const std::string& guid,
+ const base::FilePath& path,
+ uint64_t size);
+ void SendOnDownloadFailed(DownloadClient client_id,
+ const std::string& guid,
+ download::Client::FailureReason reason);
+
+ // Schedules a cleanup task in future based on status of entries.
+ void ScheduleCleanupTask();
+
+ // Posts a task to cancel the downloads in future based on the cancel_after
+ // time of the entries. If cancel time for an entry is already surpassed, the
+ // task will be posted right away which will clean the entry.
+ void ScheduleKillDownloadTaskIfNecessary();
+
+ // Kills the downloads which have surpassed their cancel_after time.
+ void KillTimedOutDownloads();
+
+ Configuration* config_;
+
+ // The directory in which the downloaded files are stored.
+ const base::FilePath download_file_dir_;
+
+ // Owned Dependencies.
+ std::unique_ptr<ClientSet> clients_;
+ std::unique_ptr<DownloadDriver> driver_;
+ std::unique_ptr<Model> model_;
+ std::unique_ptr<DeviceStatusListener> device_status_listener_;
+ std::unique_ptr<Scheduler> scheduler_;
+ std::unique_ptr<TaskScheduler> task_scheduler_;
+ std::unique_ptr<FileMonitor> file_monitor_;
+
+ // Internal state.
+ base::Closure init_callback_;
+ State controller_state_;
+ StartupStatus startup_status_;
+ std::set<std::string> externally_active_downloads_;
+ std::map<std::string, DownloadParams::StartCallback> start_callbacks_;
+ std::map<DownloadTaskType, TaskFinishedCallback> task_finished_callbacks_;
+ base::CancelableClosure cancel_downloads_callback_;
+
+ // Only used to post tasks on the same thread.
+ base::WeakPtrFactory<ControllerImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControllerImpl);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_CONTROLLER_IMPL_H_
diff --git a/chromium/components/download/internal/controller_impl_unittest.cc b/chromium/components/download/internal/controller_impl_unittest.cc
new file mode 100644
index 00000000000..5a5a7a1bbb7
--- /dev/null
+++ b/chromium/components/download/internal/controller_impl_unittest.cc
@@ -0,0 +1,1405 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/controller_impl.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/internal/client_set.h"
+#include "components/download/internal/config.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/file_monitor.h"
+#include "components/download/internal/model_impl.h"
+#include "components/download/internal/scheduler/scheduler.h"
+#include "components/download/internal/stats.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "components/download/internal/test/mock_client.h"
+#include "components/download/internal/test/test_device_status_listener.h"
+#include "components/download/internal/test/test_download_driver.h"
+#include "components/download/internal/test/test_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+using testing::SaveArg;
+
+namespace download {
+
+namespace {
+
+const base::FilePath::CharType kDownloadDirPath[] =
+ FILE_PATH_LITERAL("/test/downloads");
+
+bool GuidInEntryList(const std::vector<Entry>& entries,
+ const std::string& guid) {
+ for (const auto& entry : entries) {
+ if (entry.guid == guid)
+ return true;
+ }
+ return false;
+}
+
+DriverEntry BuildDriverEntry(const Entry& entry, DriverEntry::State state) {
+ DriverEntry dentry;
+ dentry.guid = entry.guid;
+ dentry.state = state;
+ return dentry;
+}
+
+void NotifyTaskFinished(bool success) {}
+
+class MockTaskScheduler : public TaskScheduler {
+ public:
+ MockTaskScheduler() = default;
+ ~MockTaskScheduler() override = default;
+
+ // TaskScheduler implementation.
+ MOCK_METHOD5(ScheduleTask, void(DownloadTaskType, bool, bool, long, long));
+ MOCK_METHOD1(CancelTask, void(DownloadTaskType));
+};
+
+class MockScheduler : public Scheduler {
+ public:
+ MockScheduler() = default;
+ ~MockScheduler() override = default;
+
+ MOCK_METHOD1(Reschedule, void(const Model::EntryList&));
+ MOCK_METHOD2(Next, Entry*(const Model::EntryList&, const DeviceStatus&));
+};
+
+class MockFileMonitor : public FileMonitor {
+ public:
+ MockFileMonitor() = default;
+ ~MockFileMonitor() override = default;
+
+ void TriggerInit(bool success);
+ void TriggerHardRecover(bool success);
+
+ void Initialize(const FileMonitor::InitCallback& callback) override;
+ MOCK_METHOD2(DeleteUnknownFiles,
+ void(const Model::EntryList&, const std::vector<DriverEntry>&));
+ MOCK_METHOD2(CleanupFilesForCompletedEntries,
+ std::vector<Entry*>(const Model::EntryList&,
+ const base::Closure&));
+ MOCK_METHOD2(DeleteFiles,
+ void(const std::set<base::FilePath>&, stats::FileCleanupReason));
+ void HardRecover(const FileMonitor::InitCallback&) override;
+
+ private:
+ FileMonitor::InitCallback init_callback_;
+ FileMonitor::InitCallback recover_callback_;
+};
+
+void MockFileMonitor::TriggerInit(bool success) {
+ init_callback_.Run(success);
+}
+
+void MockFileMonitor::TriggerHardRecover(bool success) {
+ recover_callback_.Run(success);
+}
+
+void MockFileMonitor::Initialize(const FileMonitor::InitCallback& callback) {
+ init_callback_ = callback;
+}
+
+void MockFileMonitor::HardRecover(const FileMonitor::InitCallback& callback) {
+ recover_callback_ = callback;
+}
+
+class DownloadServiceControllerImplTest : public testing::Test {
+ public:
+ DownloadServiceControllerImplTest()
+ : task_runner_(new base::TestSimpleTaskRunner),
+ handle_(task_runner_),
+ controller_(nullptr),
+ client_(nullptr),
+ driver_(nullptr),
+ store_(nullptr),
+ model_(nullptr),
+ device_status_listener_(nullptr),
+ scheduler_(nullptr),
+ file_monitor_(nullptr),
+ init_callback_called_(false) {
+ start_callback_ =
+ base::Bind(&DownloadServiceControllerImplTest::StartCallback,
+ base::Unretained(this));
+ }
+
+ ~DownloadServiceControllerImplTest() override = default;
+
+ void SetUp() override {
+ auto client = base::MakeUnique<test::MockClient>();
+ auto driver = base::MakeUnique<test::TestDownloadDriver>();
+ auto store = base::MakeUnique<test::TestStore>();
+ config_ = base::MakeUnique<Configuration>();
+ config_->max_retry_count = 2;
+ config_->file_keep_alive_time = base::TimeDelta::FromMinutes(10);
+ config_->file_cleanup_window = base::TimeDelta::FromMinutes(5);
+ config_->max_concurrent_downloads = 5;
+ config_->max_running_downloads = 5;
+
+ client_ = client.get();
+ driver_ = driver.get();
+ store_ = store.get();
+
+ auto clients = base::MakeUnique<DownloadClientMap>();
+ clients->insert(std::make_pair(DownloadClient::TEST, std::move(client)));
+ auto client_set = base::MakeUnique<ClientSet>(std::move(clients));
+ auto model = base::MakeUnique<ModelImpl>(std::move(store));
+ auto device_status_listener =
+ base::MakeUnique<test::TestDeviceStatusListener>();
+ auto scheduler = base::MakeUnique<NiceMock<MockScheduler>>();
+ auto task_scheduler = base::MakeUnique<MockTaskScheduler>();
+
+ auto download_file_dir = base::FilePath(kDownloadDirPath);
+ auto file_monitor = base::MakeUnique<MockFileMonitor>();
+
+ model_ = model.get();
+ device_status_listener_ = device_status_listener.get();
+ scheduler_ = scheduler.get();
+ task_scheduler_ = task_scheduler.get();
+ file_monitor_ = file_monitor.get();
+
+ controller_ = base::MakeUnique<ControllerImpl>(
+ config_.get(), std::move(client_set), std::move(driver),
+ std::move(model), std::move(device_status_listener),
+ std::move(scheduler), std::move(task_scheduler),
+ std::move(file_monitor), download_file_dir);
+ }
+
+ protected:
+ void OnInitCompleted() {
+ EXPECT_TRUE(controller_->GetState() == Controller::State::READY ||
+ controller_->GetState() == Controller::State::UNAVAILABLE);
+ init_callback_called_ = true;
+ }
+
+ void InitializeController() {
+ controller_->Initialize(
+ base::Bind(&DownloadServiceControllerImplTest::OnInitCompleted,
+ base::Unretained(this)));
+ }
+
+ DownloadParams MakeDownloadParams() {
+ DownloadParams params;
+ params.client = DownloadClient::TEST;
+ params.guid = base::GenerateGUID();
+ params.callback = start_callback_;
+ return params;
+ }
+
+ MOCK_METHOD2(StartCallback,
+ void(const std::string&, DownloadParams::StartResult));
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle handle_;
+
+ std::unique_ptr<ControllerImpl> controller_;
+ std::unique_ptr<Configuration> config_;
+ test::MockClient* client_;
+ test::TestDownloadDriver* driver_;
+ test::TestStore* store_;
+ ModelImpl* model_;
+ test::TestDeviceStatusListener* device_status_listener_;
+ MockScheduler* scheduler_;
+ MockTaskScheduler* task_scheduler_;
+ MockFileMonitor* file_monitor_;
+
+ DownloadParams::StartCallback start_callback_;
+ bool init_callback_called_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadServiceControllerImplTest);
+};
+
+} // namespace
+
+TEST_F(DownloadServiceControllerImplTest, SuccessfulInitModelFirst) {
+ base::HistogramTester histogram_tester;
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(0);
+ EXPECT_EQ(controller_->GetState(), Controller::State::CREATED);
+
+ InitializeController();
+ EXPECT_TRUE(store_->init_called());
+ EXPECT_EQ(controller_->GetState(), Controller::State::INITIALIZING);
+
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ EXPECT_EQ(controller_->GetState(), Controller::State::INITIALIZING);
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ driver_->MakeReady();
+ EXPECT_EQ(controller_->GetState(), Controller::State::READY);
+
+ task_runner_->RunUntilIdle();
+
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::SUCCESS),
+ 1);
+}
+
+TEST_F(DownloadServiceControllerImplTest, SuccessfulInitDriverFirst) {
+ base::HistogramTester histogram_tester;
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(0);
+ EXPECT_EQ(controller_->GetState(), Controller::State::CREATED);
+
+ InitializeController();
+ EXPECT_TRUE(store_->init_called());
+ EXPECT_EQ(controller_->GetState(), Controller::State::INITIALIZING);
+
+ driver_->MakeReady();
+ EXPECT_FALSE(init_callback_called_);
+ EXPECT_EQ(controller_->GetState(), Controller::State::INITIALIZING);
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ EXPECT_EQ(controller_->GetState(), Controller::State::READY);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(init_callback_called_);
+
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::SUCCESS),
+ 1);
+}
+
+TEST_F(DownloadServiceControllerImplTest, HardRecoveryAfterFailedModel) {
+ base::HistogramTester histogram_tester;
+ EXPECT_CALL(*client_, OnServiceInitialized(true, _)).Times(0);
+ EXPECT_EQ(controller_->GetState(), Controller::State::CREATED);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+
+ EXPECT_EQ(controller_->GetState(), Controller::State::RECOVERING);
+ driver_->TriggerHardRecoverComplete(true);
+ store_->TriggerHardRecover(true);
+ file_monitor_->TriggerHardRecover(true);
+
+ EXPECT_CALL(*client_, OnServiceInitialized(true, _)).Times(1);
+ task_runner_->RunUntilIdle();
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::FAILURE),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(
+ stats::StartUpResult::FAILURE_REASON_MODEL),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Recovery",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::SUCCESS),
+ 1);
+}
+
+TEST_F(DownloadServiceControllerImplTest, HardRecoveryAfterFailedFileMonitor) {
+ base::HistogramTester histogram_tester;
+ EXPECT_CALL(*client_, OnServiceInitialized(true, _)).Times(0);
+ EXPECT_EQ(controller_->GetState(), Controller::State::CREATED);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(false);
+
+ EXPECT_EQ(controller_->GetState(), Controller::State::RECOVERING);
+ driver_->TriggerHardRecoverComplete(true);
+ store_->TriggerHardRecover(true);
+ file_monitor_->TriggerHardRecover(true);
+
+ EXPECT_CALL(*client_, OnServiceInitialized(true, _)).Times(1);
+ task_runner_->RunUntilIdle();
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::FAILURE),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(
+ stats::StartUpResult::FAILURE_REASON_FILE_MONITOR),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Recovery",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::SUCCESS),
+ 1);
+}
+
+TEST_F(DownloadServiceControllerImplTest, HardRecoveryFails) {
+ base::HistogramTester histogram_tester;
+ EXPECT_CALL(*client_, OnServiceInitialized(true, _)).Times(0);
+ EXPECT_EQ(controller_->GetState(), Controller::State::CREATED);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+
+ EXPECT_EQ(controller_->GetState(), Controller::State::RECOVERING);
+ driver_->TriggerHardRecoverComplete(true);
+ store_->TriggerHardRecover(true);
+ file_monitor_->TriggerHardRecover(false);
+
+ EXPECT_CALL(*client_, OnServiceUnavailable()).Times(1);
+ task_runner_->RunUntilIdle();
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::FAILURE),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Initialization",
+ static_cast<base::HistogramBase::Sample>(
+ stats::StartUpResult::FAILURE_REASON_MODEL),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Recovery",
+ static_cast<base::HistogramBase::Sample>(stats::StartUpResult::FAILURE),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.StartUpStatus.Recovery",
+ static_cast<base::HistogramBase::Sample>(
+ stats::StartUpResult::FAILURE_REASON_FILE_MONITOR),
+ 1);
+}
+
+TEST_F(DownloadServiceControllerImplTest, SuccessfulInitWithExistingDownload) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 =
+ test::BuildEntry(DownloadClient::INVALID, base::GenerateGUID());
+
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+ std::vector<std::string> expected_guids = {entry1.guid, entry2.guid};
+
+ EXPECT_CALL(*client_,
+ OnServiceInitialized(
+ false, testing::UnorderedElementsAreArray(expected_guids)));
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(init_callback_called_);
+}
+
+TEST_F(DownloadServiceControllerImplTest, UnknownFileDeletion) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+
+ DriverEntry dentry1 =
+ BuildDriverEntry(entry1, DriverEntry::State::IN_PROGRESS);
+ DriverEntry dentry3 =
+ BuildDriverEntry(entry3, DriverEntry::State::IN_PROGRESS);
+ std::vector<DriverEntry> dentries = {dentry1, dentry3};
+
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+ EXPECT_CALL(*file_monitor_, DeleteUnknownFiles(_, _)).Times(1);
+
+ driver_->AddTestData(dentries);
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest,
+ CleanupTaskCallsFileMonitorAndSchedulesNewTaskInFuture) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+ entry3.state = Entry::State::COMPLETE;
+ entry3.completion_time = base::Time::Now();
+
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+
+ EXPECT_CALL(*file_monitor_, CleanupFilesForCompletedEntries(_, _)).Times(2);
+ EXPECT_CALL(*task_scheduler_,
+ ScheduleTask(DownloadTaskType::CLEANUP_TASK, _, _, _, _))
+ .Times(1);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ controller_->OnStartScheduledTask(DownloadTaskType::CLEANUP_TASK,
+ base::Bind(&NotifyTaskFinished));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, GetOwnerOfDownload) {
+ Entry entry = test::BuildBasicEntry();
+ std::vector<Entry> entries = {entry};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ InitializeController();
+ driver_->MakeReady();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(DownloadClient::TEST, controller_->GetOwnerOfDownload(entry.guid));
+ EXPECT_EQ(DownloadClient::INVALID,
+ controller_->GetOwnerOfDownload(base::GenerateGUID()));
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadAccepted) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ EXPECT_CALL(*this,
+ StartCallback(params.guid, DownloadParams::StartResult::ACCEPTED))
+ .Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+ controller_->StartDownload(params);
+
+ // TODO(dtrainor): Compare the full DownloadParams with the full Entry.
+ store_->TriggerUpdate(true);
+
+ std::vector<Entry> entries = store_->updated_entries();
+ Entry entry = entries[0];
+ DCHECK_EQ(entry.client, DownloadClient::TEST);
+ EXPECT_TRUE(base::StartsWith(entry.target_file_path.value(), kDownloadDirPath,
+ base::CompareCase::SENSITIVE));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadFailsWithBackoff) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ Entry entry = test::BuildBasicEntry();
+ std::vector<Entry> entries = {entry};
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Set the failure expectations.
+ config_->max_scheduled_downloads = 1U;
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ EXPECT_CALL(*this,
+ StartCallback(params.guid, DownloadParams::StartResult::BACKOFF))
+ .Times(1);
+ controller_->StartDownload(params);
+
+ EXPECT_FALSE(GuidInEntryList(store_->updated_entries(), params.guid));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest,
+ AddDownloadFailsWithDuplicateGuidInModel) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ Entry entry = test::BuildBasicEntry();
+ std::vector<Entry> entries = {entry};
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ params.guid = entry.guid;
+ EXPECT_CALL(
+ *this,
+ StartCallback(params.guid, DownloadParams::StartResult::UNEXPECTED_GUID))
+ .Times(1);
+ controller_->StartDownload(params);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadFailsWithDuplicateCall) {
+ testing::InSequence sequence;
+
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ // Trigger the download twice.
+ DownloadParams params = MakeDownloadParams();
+ EXPECT_CALL(
+ *this,
+ StartCallback(params.guid, DownloadParams::StartResult::UNEXPECTED_GUID))
+ .Times(1);
+ EXPECT_CALL(*this,
+ StartCallback(params.guid, DownloadParams::StartResult::ACCEPTED))
+ .Times(1);
+ controller_->StartDownload(params);
+ controller_->StartDownload(params);
+ store_->TriggerUpdate(true);
+
+ EXPECT_TRUE(GuidInEntryList(store_->updated_entries(), params.guid));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadFailsWithBadClient) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ params.client = DownloadClient::INVALID;
+ EXPECT_CALL(*this,
+ StartCallback(params.guid,
+ DownloadParams::StartResult::UNEXPECTED_CLIENT))
+ .Times(1);
+ controller_->StartDownload(params);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadFailsWithClientCancel) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ EXPECT_CALL(
+ *this,
+ StartCallback(params.guid, DownloadParams::StartResult::CLIENT_CANCELLED))
+ .Times(1);
+ controller_->StartDownload(params);
+
+ controller_->CancelDownload(params.guid);
+ store_->TriggerUpdate(true);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, AddDownloadFailsWithInternalError) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ // Set up the Controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Trigger the download.
+ DownloadParams params = MakeDownloadParams();
+ EXPECT_CALL(*this, StartCallback(params.guid,
+ DownloadParams::StartResult::INTERNAL_ERROR))
+ .Times(1);
+ controller_->StartDownload(params);
+
+ store_->TriggerUpdate(false);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, Pause) {
+ // Setup download service test data.
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+ entry1.state = Entry::State::AVAILABLE;
+ entry2.state = Entry::State::ACTIVE;
+ entry3.state = Entry::State::COMPLETE;
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(3);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(3);
+
+ // Set the network status to disconnected so no entries will be polled from
+ // the scheduler.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ // Setup download driver test data.
+ DriverEntry driver_entry1, driver_entry2, driver_entry3;
+ driver_entry1.guid = entry1.guid;
+ driver_entry1.state = DriverEntry::State::IN_PROGRESS;
+ driver_entry2.guid = entry2.guid;
+ driver_entry2.state = DriverEntry::State::IN_PROGRESS;
+ driver_entry3.guid = entry3.guid;
+ driver_->AddTestData(
+ std::vector<DriverEntry>{driver_entry1, driver_entry2, driver_entry3});
+
+ // Pause in progress available entry.
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entry1.guid)->state);
+ controller_->PauseDownload(entry1.guid);
+ EXPECT_TRUE(driver_->Find(entry1.guid)->paused);
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entry1.guid)->state);
+
+ // Pause in progress active entry.
+ controller_->PauseDownload(entry2.guid);
+ EXPECT_TRUE(driver_->Find(entry2.guid)->paused);
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entry2.guid)->state);
+
+ // Entries in complete states can't be paused.
+ controller_->PauseDownload(entry3.guid);
+ EXPECT_FALSE(driver_->Find(entry3.guid)->paused);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entry3.guid)->state);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, Resume) {
+ // Setup download service test data.
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ entry1.state = Entry::State::PAUSED;
+ entry2.state = Entry::State::ACTIVE;
+ std::vector<Entry> entries = {entry1, entry2};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(2);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(2);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ // Setup download driver test data.
+ DriverEntry driver_entry1, driver_entry2;
+ driver_entry1.guid = entry1.guid;
+ driver_entry1.paused = true;
+ driver_entry2.guid = entry2.guid;
+ driver_entry2.paused = false;
+ driver_->AddTestData(std::vector<DriverEntry>{driver_entry1, driver_entry2});
+
+ // Resume the paused download.
+ device_status_listener_->SetDeviceStatus(
+ DeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entry1.guid)->state);
+ controller_->ResumeDownload(entry1.guid);
+ EXPECT_FALSE(driver_->Find(entry1.guid)->paused);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry1.guid)->state);
+
+ // Entries in paused state can't be resumed.
+ controller_->ResumeDownload(entry2.guid);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry2.guid)->state);
+ EXPECT_FALSE(driver_->Find(entry2.guid)->paused);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, Cancel) {
+ Entry entry = test::BuildBasicEntry();
+ entry.state = Entry::State::ACTIVE;
+ std::vector<Entry> entries = {entry};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry.guid, Client::FailureReason::CANCELLED))
+ .Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(2);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(2);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ DriverEntry driver_entry;
+ driver_entry.guid = entry.guid;
+ driver_->AddTestData(std::vector<DriverEntry>{driver_entry});
+
+ controller_->CancelDownload(entry.guid);
+ EXPECT_EQ(nullptr, model_->Get(entry.guid));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, OnDownloadFailed) {
+ Entry entry = test::BuildBasicEntry();
+ entry.state = Entry::State::ACTIVE;
+ std::vector<Entry> entries = {entry};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry.guid, Client::FailureReason::NETWORK))
+ .Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(2);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(2);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ DriverEntry driver_entry;
+ driver_entry.guid = entry.guid;
+
+ driver_->NotifyDownloadFailed(driver_entry, FailureType::NOT_RECOVERABLE);
+ EXPECT_EQ(nullptr, model_->Get(entry.guid));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, RetryOnFailure) {
+ Entry entry1 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ std::vector<Entry> entries = {entry1, entry2};
+
+ DriverEntry dentry1 =
+ BuildDriverEntry(entry1, DriverEntry::State::INTERRUPTED);
+ DriverEntry dentry2 =
+ BuildDriverEntry(entry2, DriverEntry::State::INTERRUPTED);
+ std::vector<DriverEntry> dentries = {dentry1, dentry2};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Set up the Controller.
+ device_status_listener_->SetDeviceStatus(
+ DeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+
+ driver_->AddTestData(dentries);
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Test retry on failure.
+ config_->max_retry_count = 4;
+ EXPECT_CALL(*client_, OnDownloadSucceeded(entry1.guid, _, _)).Times(1);
+ base::FilePath path = base::FilePath::FromUTF8Unsafe("123");
+ driver_->NotifyDownloadFailed(dentry1, FailureType::RECOVERABLE);
+ driver_->NotifyDownloadFailed(dentry1, FailureType::RECOVERABLE);
+ driver_->NotifyDownloadSucceeded(dentry1);
+
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry2.guid, Client::FailureReason::NETWORK))
+ .Times(1);
+ driver_->NotifyDownloadFailed(dentry2, FailureType::RECOVERABLE);
+ driver_->NotifyDownloadFailed(dentry2, FailureType::RECOVERABLE);
+ driver_->NotifyDownloadFailed(dentry2, FailureType::RECOVERABLE);
+ driver_->NotifyDownloadFailed(dentry2, FailureType::RECOVERABLE);
+ // Failed entry should exist because we retry after a delay.
+ EXPECT_NE(nullptr, model_->Get(entry2.guid));
+
+ task_runner_->RunUntilIdle();
+ // Retry is done, and failed entry should be removed.
+ EXPECT_EQ(nullptr, model_->Get(entry2.guid));
+}
+
+TEST_F(DownloadServiceControllerImplTest, OnDownloadSucceeded) {
+ Entry entry = test::BuildBasicEntry();
+ entry.state = Entry::State::ACTIVE;
+ std::vector<Entry> entries = {entry};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*client_, OnDownloadSucceeded(entry.guid, _, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(2);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(2);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ DriverEntry driver_entry;
+ driver_entry.guid = entry.guid;
+ driver_entry.bytes_downloaded = 1024;
+ driver_entry.completion_time = base::Time::Now();
+ driver_entry.current_file_path = base::FilePath::FromUTF8Unsafe("123");
+
+ long start_time = 0;
+ EXPECT_CALL(*task_scheduler_,
+ ScheduleTask(DownloadTaskType::CLEANUP_TASK, _, _, _, _))
+ .WillOnce(SaveArg<3>(&start_time));
+ driver_->NotifyDownloadSucceeded(driver_entry);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entry.guid)->state);
+ EXPECT_LE(driver_entry.completion_time + config_->file_keep_alive_time,
+ base::Time::Now() + base::TimeDelta::FromSeconds(start_time));
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, CleanupTaskScheduledAtEarliestTime) {
+ Entry entry1 = test::BuildBasicEntry();
+ entry1.state = Entry::State::ACTIVE;
+ entry1.completion_time = base::Time::Now() - base::TimeDelta::FromMinutes(1);
+ Entry entry2 = test::BuildBasicEntry();
+ entry2.state = Entry::State::COMPLETE;
+ entry2.completion_time = base::Time::Now() - base::TimeDelta::FromMinutes(2);
+ std::vector<Entry> entries = {entry1, entry2};
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ DriverEntry driver_entry;
+ driver_entry.guid = entry1.guid;
+ driver_entry.bytes_downloaded = 1024;
+ driver_entry.completion_time = base::Time::Now();
+ driver_entry.current_file_path = base::FilePath::FromUTF8Unsafe("123");
+
+ EXPECT_CALL(*task_scheduler_, ScheduleTask(DownloadTaskType::CLEANUP_TASK,
+ false, false, 480, 780))
+ .Times(1);
+ driver_->NotifyDownloadSucceeded(driver_entry);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entry1.guid)->state);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, OnDownloadUpdated) {
+ Entry entry = test::BuildBasicEntry();
+ entry.state = Entry::State::ACTIVE;
+ std::vector<Entry> entries = {entry};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(1);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+
+ DriverEntry driver_entry;
+ driver_entry.state = DriverEntry::State::IN_PROGRESS;
+ driver_entry.guid = entry.guid;
+ driver_entry.bytes_downloaded = 1024;
+
+ EXPECT_CALL(*client_,
+ OnDownloadUpdated(entry.guid, driver_entry.bytes_downloaded));
+ driver_->NotifyDownloadUpdate(driver_entry);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry.guid)->state);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, DownloadCompletionTest) {
+ // TODO(dtrainor): Simulate a UNKNOWN once that is supported.
+
+ Entry entry1 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry3 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry4 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ entry4.scheduling_params.cancel_time = base::Time::Now();
+
+ DriverEntry dentry1 =
+ BuildDriverEntry(entry1, DriverEntry::State::IN_PROGRESS);
+ // dentry2 will effectively be created by the test to simulate a start
+ // download.
+ DriverEntry dentry3 =
+ BuildDriverEntry(entry3, DriverEntry::State::IN_PROGRESS);
+
+ std::vector<Entry> entries = {entry1, entry2, entry3, entry4};
+ std::vector<DriverEntry> dentries = {dentry1, dentry3};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Test FailureReason::TIMEDOUT.
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry4.guid, Client::FailureReason::TIMEDOUT))
+ .Times(1);
+
+ // Set up the Controller.
+ driver_->AddTestData(dentries);
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // Test FailureReason::CANCELLED.
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry1.guid, Client::FailureReason::CANCELLED))
+ .Times(1);
+ controller_->CancelDownload(entry1.guid);
+
+ // Test FailureReason::ABORTED.
+ EXPECT_CALL(*client_, OnDownloadStarted(entry2.guid, _, _))
+ .Times(1)
+ .WillOnce(Return(Client::ShouldDownload::ABORT));
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry2.guid, Client::FailureReason::ABORTED))
+ .Times(1);
+ driver_->Start(RequestParams(), entry2.guid, entry2.target_file_path,
+ NO_TRAFFIC_ANNOTATION_YET);
+
+ // Test FailureReason::NETWORK.
+ EXPECT_CALL(*client_,
+ OnDownloadFailed(entry3.guid, Client::FailureReason::NETWORK))
+ .Times(1);
+ driver_->NotifyDownloadFailed(dentry3, FailureType::NOT_RECOVERABLE);
+
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(DownloadServiceControllerImplTest, StartupRecovery) {
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ std::vector<Entry> entries;
+ std::vector<DriverEntry> driver_entries;
+ entries.push_back(test::BuildBasicEntry(Entry::State::NEW));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::IN_PROGRESS));
+ entries.push_back(test::BuildBasicEntry(Entry::State::NEW));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::COMPLETE));
+ entries.push_back(test::BuildBasicEntry(Entry::State::NEW));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::CANCELLED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::NEW));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::INTERRUPTED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::NEW));
+
+ entries.push_back(test::BuildBasicEntry(Entry::State::AVAILABLE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::IN_PROGRESS));
+ entries.push_back(test::BuildBasicEntry(Entry::State::AVAILABLE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::COMPLETE));
+ entries.push_back(test::BuildBasicEntry(Entry::State::AVAILABLE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::CANCELLED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::AVAILABLE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::INTERRUPTED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::AVAILABLE));
+
+ entries.push_back(test::BuildBasicEntry(Entry::State::ACTIVE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::IN_PROGRESS));
+ entries.push_back(test::BuildBasicEntry(Entry::State::ACTIVE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::COMPLETE));
+ entries.push_back(test::BuildBasicEntry(Entry::State::ACTIVE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::CANCELLED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::ACTIVE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::INTERRUPTED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::ACTIVE));
+
+ entries.push_back(test::BuildBasicEntry(Entry::State::PAUSED));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::IN_PROGRESS));
+ entries.push_back(test::BuildBasicEntry(Entry::State::PAUSED));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::COMPLETE));
+ entries.push_back(test::BuildBasicEntry(Entry::State::PAUSED));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::CANCELLED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::PAUSED));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::INTERRUPTED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::PAUSED));
+
+ entries.push_back(test::BuildBasicEntry(Entry::State::COMPLETE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::IN_PROGRESS));
+ entries.push_back(test::BuildBasicEntry(Entry::State::COMPLETE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::COMPLETE));
+ entries.push_back(test::BuildBasicEntry(Entry::State::COMPLETE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::CANCELLED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::COMPLETE));
+ driver_entries.push_back(
+ BuildDriverEntry(entries.back(), DriverEntry::State::INTERRUPTED));
+ entries.push_back(test::BuildBasicEntry(Entry::State::COMPLETE));
+
+ // Set up the Controller.
+ device_status_listener_->SetDeviceStatus(
+ DeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+
+ InitializeController();
+ driver_->AddTestData(driver_entries);
+ driver_->MakeReady();
+ store_->AutomaticallyTriggerAllFutureCallbacks(true);
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+
+ // Allow the initialization routines and persistent layers to do their thing.
+ task_runner_->RunUntilIdle();
+
+ // Validate Model and DownloadDriver states.
+ // Note that we are accessing the Model instead of the Store here to make it
+ // easier to query the states.
+ // TODO(dtrainor): Check more of the DriverEntry state to validate that the
+ // entries are either paused or resumed accordingly.
+
+ // Entry::State::NEW.
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[0].guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[1].guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[2].guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[3].guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[4].guid)->state);
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[0].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[1].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[2].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[3].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[4].guid));
+
+ // Entry::State::AVAILABLE.
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entries[5].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[6].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[7].guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entries[8].guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entries[9].guid)->state);
+ EXPECT_NE(base::nullopt, driver_->Find(entries[5].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[6].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[7].guid));
+ EXPECT_NE(base::nullopt, driver_->Find(entries[8].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[9].guid));
+
+ // Entry::State::ACTIVE.
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entries[10].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[11].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[12].guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entries[13].guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entries[14].guid)->state);
+ EXPECT_NE(base::nullopt, driver_->Find(entries[10].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[11].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[12].guid));
+ EXPECT_NE(base::nullopt, driver_->Find(entries[13].guid));
+ EXPECT_NE(base::nullopt, driver_->Find(entries[14].guid));
+
+ // Entry::State::PAUSED.
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entries[15].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[16].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[17].guid)->state);
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entries[18].guid)->state);
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entries[19].guid)->state);
+ EXPECT_NE(base::nullopt, driver_->Find(entries[15].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[16].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[17].guid));
+ EXPECT_NE(base::nullopt, driver_->Find(entries[18].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[19].guid));
+
+ // prog, comp, canc, int, __
+ // Entry::State::COMPLETE.
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[20].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[21].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[22].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[23].guid)->state);
+ EXPECT_EQ(Entry::State::COMPLETE, model_->Get(entries[24].guid)->state);
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[20].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[21].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[22].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[23].guid));
+ EXPECT_EQ(base::nullopt, driver_->Find(entries[24].guid));
+}
+
+TEST_F(DownloadServiceControllerImplTest, ExistingExternalDownload) {
+ Entry entry1 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry3 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ entry3.scheduling_params.priority = SchedulingParams::Priority::UI;
+
+ // Simulate an existing download the service knows about and one it does not.
+ DriverEntry dentry1 =
+ BuildDriverEntry(entry2, DriverEntry::State::IN_PROGRESS);
+ DriverEntry dentry2;
+ dentry2.guid = base::GenerateGUID();
+ dentry2.state = DriverEntry::State::IN_PROGRESS;
+
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+ std::vector<DriverEntry> dentries = {dentry1, dentry2};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Set up the Controller.
+ device_status_listener_->SetDeviceStatus(
+ DeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+
+ driver_->AddTestData(dentries);
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry1.guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry2.guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry3.guid)->state);
+
+ EXPECT_FALSE(driver_->Find(entry1.guid).has_value());
+
+ EXPECT_TRUE(driver_->Find(entry2.guid).has_value());
+ EXPECT_TRUE(driver_->Find(entry2.guid).value().paused);
+
+ EXPECT_TRUE(driver_->Find(entry3.guid).has_value());
+ EXPECT_FALSE(driver_->Find(entry3.guid).value().paused);
+
+ // Simulate a successful external download.
+ driver_->NotifyDownloadSucceeded(dentry2);
+
+ EXPECT_TRUE(driver_->Find(entry1.guid).has_value());
+ EXPECT_FALSE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry3.guid).value().paused);
+}
+
+TEST_F(DownloadServiceControllerImplTest, NewExternalDownload) {
+ Entry entry1 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ entry2.scheduling_params.priority = SchedulingParams::Priority::UI;
+
+ DriverEntry dentry1 =
+ BuildDriverEntry(entry2, DriverEntry::State::IN_PROGRESS);
+
+ std::vector<Entry> entries = {entry1, entry2};
+ std::vector<DriverEntry> dentries = {dentry1};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Set up the Controller.
+ device_status_listener_->SetDeviceStatus(
+ DeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+
+ driver_->AddTestData(dentries);
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry1.guid)->state);
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry2.guid)->state);
+
+ EXPECT_TRUE(driver_->Find(entry1.guid).has_value());
+ EXPECT_FALSE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_TRUE(driver_->Find(entry2.guid).has_value());
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ DriverEntry dentry2;
+ dentry2.guid = base::GenerateGUID();
+ dentry2.state = DriverEntry::State::IN_PROGRESS;
+
+ // Simulate a newly created external download.
+ driver_->Start(RequestParams(), dentry2.guid, dentry2.current_file_path,
+ NO_TRAFFIC_ANNOTATION_YET);
+
+ EXPECT_TRUE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ // Simulate a paused external download.
+ dentry2.paused = true;
+ driver_->NotifyDownloadUpdate(dentry2);
+
+ EXPECT_FALSE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ // Simulate a resumed external download.
+ dentry2.paused = false;
+ driver_->NotifyDownloadUpdate(dentry2);
+
+ EXPECT_TRUE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ // Simulate a failed external download.
+ dentry2.state = DriverEntry::State::INTERRUPTED;
+ driver_->NotifyDownloadFailed(dentry2, FailureType::RECOVERABLE);
+
+ EXPECT_FALSE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ // Rebuild the download so we can simulate more.
+ dentry2.state = DriverEntry::State::IN_PROGRESS;
+ driver_->Start(RequestParams(), dentry2.guid, dentry2.current_file_path,
+ NO_TRAFFIC_ANNOTATION_YET);
+
+ EXPECT_TRUE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+
+ // Simulate a successful external download.
+ dentry2.state = DriverEntry::State::COMPLETE;
+ driver_->NotifyDownloadSucceeded(dentry2);
+
+ EXPECT_FALSE(driver_->Find(entry1.guid).value().paused);
+ EXPECT_FALSE(driver_->Find(entry2.guid).value().paused);
+}
+
+TEST_F(DownloadServiceControllerImplTest, CancelTimeTest) {
+ Entry entry1 = test::BuildBasicEntry();
+ entry1.state = Entry::State::ACTIVE;
+ entry1.create_time = base::Time::Now() - base::TimeDelta::FromSeconds(10);
+ entry1.scheduling_params.cancel_time =
+ base::Time::Now() - base::TimeDelta::FromSeconds(5);
+
+ Entry entry2 = test::BuildBasicEntry();
+ entry2.state = Entry::State::COMPLETE;
+ entry2.create_time = base::Time::Now() - base::TimeDelta::FromSeconds(10);
+ entry2.scheduling_params.cancel_time =
+ base::Time::Now() - base::TimeDelta::FromSeconds(2);
+ std::vector<Entry> entries = {entry1, entry2};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ // At startup, timed out entries should be killed.
+ std::vector<Entry*> updated_entries = model_->PeekEntries();
+ EXPECT_EQ(1u, updated_entries.size());
+}
+
+// Ensures no more downloads are activated if the number of downloads exceeds
+// the max running download configuration.
+TEST_F(DownloadServiceControllerImplTest, ThrottlingConfigMaxRunning) {
+ Entry entry1 = test::BuildBasicEntry(Entry::State::AVAILABLE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::ACTIVE);
+ std::vector<Entry> entries = {entry1, entry2};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Setup the Configuration.
+ config_->max_concurrent_downloads = 1u;
+ config_->max_running_downloads = 1u;
+
+ // Setup the controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ store_->AutomaticallyTriggerAllFutureCallbacks(true);
+
+ // Hit the max running configuration threshold, nothing should be called.
+ EXPECT_CALL(*scheduler_, Next(_, _)).Times(0);
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entry1.guid)->state);
+}
+
+// Ensures max concurrent download configuration considers both active and
+// paused downloads.
+TEST_F(DownloadServiceControllerImplTest, ThrottlingConfigMaxConcurrent) {
+ Entry entry1 = test::BuildBasicEntry(Entry::State::AVAILABLE);
+ Entry entry2 = test::BuildBasicEntry(Entry::State::AVAILABLE);
+ Entry entry3 = test::BuildBasicEntry(Entry::State::PAUSED);
+ std::vector<Entry> entries = {entry1, entry2, entry3};
+
+ EXPECT_CALL(*client_, OnServiceInitialized(false, _)).Times(1);
+
+ // Setup the Configuration.
+ config_->max_concurrent_downloads = 2u;
+ config_->max_running_downloads = 1u;
+
+ // Setup the controller.
+ InitializeController();
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ file_monitor_->TriggerInit(true);
+ store_->AutomaticallyTriggerAllFutureCallbacks(true);
+
+ // Can have one more download due to max concurrent configuration.
+ testing::InSequence seq;
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entry1.guid)->state);
+ EXPECT_CALL(*scheduler_, Next(_, _))
+ .Times(1)
+ .WillOnce(Return(model_->Get(entry1.guid)))
+ .RetiresOnSaturation();
+ // |scheduler_| will poll entry2 on next time, but it should not change the
+ // state of entry2 due to max running download configuration.
+ ON_CALL(*scheduler_, Next(_, _))
+ .WillByDefault(Return(model_->Get(entry2.guid)));
+
+ EXPECT_CALL(*scheduler_, Reschedule(_)).Times(1);
+ driver_->MakeReady();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(Entry::State::ACTIVE, model_->Get(entry1.guid)->state);
+ EXPECT_EQ(Entry::State::AVAILABLE, model_->Get(entry2.guid)->state);
+ EXPECT_EQ(Entry::State::PAUSED, model_->Get(entry3.guid)->state);
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/download_driver.h b/chromium/components/download/internal/download_driver.h
index 88247d18d5d..40bac08f14d 100644
--- a/chromium/components/download/internal/download_driver.h
+++ b/chromium/components/download/internal/download_driver.h
@@ -5,10 +5,12 @@
#ifndef COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_DRIVER_H_
#define COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_DRIVER_H_
+#include <set>
#include <string>
#include "base/optional.h"
#include "components/download/internal/driver_entry.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
namespace base {
class FilePath;
@@ -16,7 +18,16 @@ class FilePath;
namespace download {
-struct DownloadParams;
+struct RequestParams;
+
+// Indicates the recovery type of a download.
+enum class FailureType {
+ // Download failed due to an irrecoverable error.
+ NOT_RECOVERABLE = 0,
+
+ // Download failed, but we might be able to recover if we try again.
+ RECOVERABLE = 1,
+};
// The interface that includes all the operations to interact with low level
// download library functionalities.
@@ -31,35 +42,49 @@ class DownloadDriver {
// when the low level download library is ready.
virtual void OnDriverReady(bool success) = 0;
+ // Called asynchronously in response to a DownloadDriver::HardRecover call.
+ // If |success| is |false|, recovery of the DownloadDriver failed.
+ virtual void OnDriverHardRecoverComplete(bool success) = 0;
+
// Called when any download is created.
virtual void OnDownloadCreated(const DriverEntry& download) = 0;
// Called when any download is failed. |reason| is propagated from low level
// download library.
- virtual void OnDownloadFailed(const DriverEntry& download, int reason) = 0;
+ virtual void OnDownloadFailed(const DriverEntry& download,
+ FailureType failure_type) = 0;
// Called when any download is successfully completed.
- virtual void OnDownloadSucceeded(const DriverEntry& download,
- const base::FilePath& path) = 0;
+ virtual void OnDownloadSucceeded(const DriverEntry& download) = 0;
// Called when any download is updated.
virtual void OnDownloadUpdated(const DriverEntry& download) = 0;
};
+ virtual ~DownloadDriver() = default;
+
// Initialize the driver to receive download updates.
virtual void Initialize(Client* client) = 0;
- // Returns if the driver is ready. Returns false when the driver is not
- // initialized by the client, or low level download library has been shut
- // down.
+ // Attempts to clean up and reset the DownloadDriver. It should remove all
+ // state relevant to the DownloadService.
+ virtual void HardRecover() = 0;
+
+ // Returns if the driver is ready after the low level library has loaded all
+ // the data. Returns false when the driver is not initialized by the client,
+ // or low level download library has been shut down.
virtual bool IsReady() const = 0;
// Starts a new download.
- virtual void Start(const DownloadParams& params) = 0;
+ virtual void Start(
+ const RequestParams& request_params,
+ const std::string& guid,
+ const base::FilePath& file_path,
+ const net::NetworkTrafficAnnotationTag& traffic_annotation) = 0;
// Cancels an existing download, all data associated with this download should
// be removed.
- virtual void Cancel(const std::string& guid) = 0;
+ virtual void Remove(const std::string& guid) = 0;
// Pauses the download.
virtual void Pause(const std::string& guid) = 0;
@@ -67,8 +92,12 @@ class DownloadDriver {
// Resumes the download
virtual void Resume(const std::string& guid) = 0;
- // Find a download record from low level download library.
+ // Finds a download record from low level download library.
virtual base::Optional<DriverEntry> Find(const std::string& guid) = 0;
+
+ // Called to query the current set of active downloads. This doesn't
+ // necessarily mean downloads started by the service.
+ virtual std::set<std::string> GetActiveDownloads() = 0;
};
} // namespace download
diff --git a/chromium/components/download/internal/download_service_impl.cc b/chromium/components/download/internal/download_service_impl.cc
index 44610a9a208..ceff12d729f 100644
--- a/chromium/components/download/internal/download_service_impl.cc
+++ b/chromium/components/download/internal/download_service_impl.cc
@@ -4,27 +4,158 @@
#include "components/download/internal/download_service_impl.h"
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "components/download/internal/controller.h"
+#include "components/download/internal/startup_status.h"
+#include "components/download/internal/stats.h"
+
namespace download {
-// static
-DownloadService* DownloadService::Create(
- const base::FilePath& storage_dir,
- const scoped_refptr<base::SequencedTaskRunner>& background_task_runner) {
- return new DownloadServiceImpl(Configuration::CreateFromFinch());
+DownloadServiceImpl::DownloadServiceImpl(std::unique_ptr<Configuration> config,
+ std::unique_ptr<Controller> controller)
+ : config_(std::move(config)),
+ controller_(std::move(controller)),
+ service_config_(config_.get()),
+ startup_completed_(false) {
+ controller_->Initialize(base::Bind(
+ &DownloadServiceImpl::OnControllerInitialized, base::Unretained(this)));
}
-DownloadServiceImpl::DownloadServiceImpl(std::unique_ptr<Configuration> config)
- : config_(std::move(config)) {}
-
DownloadServiceImpl::~DownloadServiceImpl() = default;
+const ServiceConfig& DownloadServiceImpl::GetConfig() {
+ return service_config_;
+}
+
+void DownloadServiceImpl::OnStartScheduledTask(
+ DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) {
+ if (startup_completed_) {
+ controller_->OnStartScheduledTask(task_type, callback);
+ return;
+ }
+
+ pending_tasks_[task_type] =
+ base::Bind(&Controller::OnStartScheduledTask,
+ base::Unretained(controller_.get()), task_type, callback);
+}
+
+bool DownloadServiceImpl::OnStopScheduledTask(DownloadTaskType task_type) {
+ if (startup_completed_) {
+ return controller_->OnStopScheduledTask(task_type);
+ }
+
+ auto iter = pending_tasks_.find(task_type);
+ if (iter != pending_tasks_.end()) {
+ // We still need to run the callback in order to properly cleanup and notify
+ // the system by running the respective task finished callbacks.
+ iter->second.Run();
+ pending_tasks_.erase(iter);
+ }
+
+ return true;
+}
+
+DownloadService::ServiceStatus DownloadServiceImpl::GetStatus() {
+ switch (controller_->GetState()) {
+ case Controller::State::CREATED: // Intentional fallthrough.
+ case Controller::State::INITIALIZING: // Intentional fallthrough.
+ case Controller::State::RECOVERING:
+ return DownloadService::ServiceStatus::STARTING_UP;
+ case Controller::State::READY:
+ return DownloadService::ServiceStatus::READY;
+ case Controller::State::UNAVAILABLE: // Intentional fallthrough.
+ default:
+ return DownloadService::ServiceStatus::UNAVAILABLE;
+ }
+}
+
void DownloadServiceImpl::StartDownload(const DownloadParams& download_params) {
+ stats::LogServiceApiAction(download_params.client,
+ stats::ServiceApiAction::START_DOWNLOAD);
+ DCHECK_EQ(download_params.guid, base::ToUpperASCII(download_params.guid));
+ stats::LogDownloadParams(download_params);
+ if (startup_completed_) {
+ controller_->StartDownload(download_params);
+ } else {
+ pending_actions_.push_back(base::Bind(&Controller::StartDownload,
+ base::Unretained(controller_.get()),
+ download_params));
+ }
}
-void DownloadServiceImpl::PauseDownload(const std::string& guid) {}
-void DownloadServiceImpl::ResumeDownload(const std::string& guid) {}
-void DownloadServiceImpl::CancelDownload(const std::string& guid) {}
+
+void DownloadServiceImpl::PauseDownload(const std::string& guid) {
+ stats::LogServiceApiAction(controller_->GetOwnerOfDownload(guid),
+ stats::ServiceApiAction::PAUSE_DOWNLOAD);
+ DCHECK_EQ(guid, base::ToUpperASCII(guid));
+
+ if (startup_completed_) {
+ controller_->PauseDownload(guid);
+ } else {
+ pending_actions_.push_back(base::Bind(
+ &Controller::PauseDownload, base::Unretained(controller_.get()), guid));
+ }
+}
+
+void DownloadServiceImpl::ResumeDownload(const std::string& guid) {
+ stats::LogServiceApiAction(controller_->GetOwnerOfDownload(guid),
+ stats::ServiceApiAction::RESUME_DOWNLOAD);
+ DCHECK_EQ(guid, base::ToUpperASCII(guid));
+
+ if (startup_completed_) {
+ controller_->ResumeDownload(guid);
+ } else {
+ pending_actions_.push_back(base::Bind(&Controller::ResumeDownload,
+ base::Unretained(controller_.get()),
+ guid));
+ }
+}
+
+void DownloadServiceImpl::CancelDownload(const std::string& guid) {
+ stats::LogServiceApiAction(controller_->GetOwnerOfDownload(guid),
+ stats::ServiceApiAction::CANCEL_DOWNLOAD);
+ DCHECK_EQ(guid, base::ToUpperASCII(guid));
+
+ if (startup_completed_) {
+ controller_->CancelDownload(guid);
+ } else {
+ pending_actions_.push_back(base::Bind(&Controller::CancelDownload,
+ base::Unretained(controller_.get()),
+ guid));
+ }
+}
+
void DownloadServiceImpl::ChangeDownloadCriteria(
const std::string& guid,
- const SchedulingParams& params) {}
+ const SchedulingParams& params) {
+ stats::LogServiceApiAction(controller_->GetOwnerOfDownload(guid),
+ stats::ServiceApiAction::CHANGE_CRITERIA);
+ DCHECK_EQ(guid, base::ToUpperASCII(guid));
+
+ if (startup_completed_) {
+ controller_->ChangeDownloadCriteria(guid, params);
+ } else {
+ pending_actions_.push_back(base::Bind(&Controller::ChangeDownloadCriteria,
+ base::Unretained(controller_.get()),
+ guid, params));
+ }
+}
+
+void DownloadServiceImpl::OnControllerInitialized() {
+ while (!pending_actions_.empty()) {
+ auto callback = pending_actions_.front();
+ callback.Run();
+ pending_actions_.pop_front();
+ }
+
+ while (!pending_tasks_.empty()) {
+ auto iter = pending_tasks_.begin();
+ iter->second.Run();
+ pending_tasks_.erase(iter);
+ }
+
+ startup_completed_ = true;
+}
} // namespace download
diff --git a/chromium/components/download/internal/download_service_impl.h b/chromium/components/download/internal/download_service_impl.h
index 319aea1976b..0333f9d0b1f 100644
--- a/chromium/components/download/internal/download_service_impl.h
+++ b/chromium/components/download/internal/download_service_impl.h
@@ -5,25 +5,35 @@
#ifndef COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_SERVICE_IMPL_H_
#define COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_SERVICE_IMPL_H_
+#include <deque>
+#include <map>
#include <memory>
#include <string>
#include "base/macros.h"
#include "components/download/internal/config.h"
+#include "components/download/internal/service_config_impl.h"
#include "components/download/public/download_service.h"
namespace download {
+class Controller;
struct DownloadParams;
struct SchedulingParams;
// The internal implementation of the DownloadService.
class DownloadServiceImpl : public DownloadService {
public:
- DownloadServiceImpl(std::unique_ptr<Configuration> config);
+ DownloadServiceImpl(std::unique_ptr<Configuration> config,
+ std::unique_ptr<Controller> controller);
~DownloadServiceImpl() override;
// DownloadService implementation.
+ const ServiceConfig& GetConfig() override;
+ void OnStartScheduledTask(DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) override;
+ bool OnStopScheduledTask(DownloadTaskType task_type) override;
+ ServiceStatus GetStatus() override;
void StartDownload(const DownloadParams& download_params) override;
void PauseDownload(const std::string& guid) override;
void ResumeDownload(const std::string& guid) override;
@@ -32,8 +42,19 @@ class DownloadServiceImpl : public DownloadService {
const SchedulingParams& params) override;
private:
+ void OnControllerInitialized();
+
+ // config_ needs to be destructed after controller_ and service_config_ which
+ // hold onto references to it.
std::unique_ptr<Configuration> config_;
+ std::unique_ptr<Controller> controller_;
+ ServiceConfigImpl service_config_;
+
+ std::deque<base::Closure> pending_actions_;
+ std::map<DownloadTaskType, base::Closure> pending_tasks_;
+ bool startup_completed_;
+
DISALLOW_COPY_AND_ASSIGN(DownloadServiceImpl);
};
diff --git a/chromium/components/download/internal/download_service_impl_unittest.cc b/chromium/components/download/internal/download_service_impl_unittest.cc
new file mode 100644
index 00000000000..74090dfbca8
--- /dev/null
+++ b/chromium/components/download/internal/download_service_impl_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/download_service_impl.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/internal/startup_status.h"
+#include "components/download/internal/stats.h"
+#include "components/download/internal/test/download_params_utils.h"
+#include "components/download/internal/test/mock_controller.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Return;
+
+namespace download {
+namespace {
+
+class DownloadServiceImplTest : public testing::Test {
+ public:
+ DownloadServiceImplTest()
+ : controller_(nullptr),
+ task_runner_(new base::TestSimpleTaskRunner),
+ handle_(task_runner_) {}
+ ~DownloadServiceImplTest() override = default;
+
+ void SetUp() override {
+ auto config = base::MakeUnique<Configuration>();
+ auto controller = base::MakeUnique<test::MockController>();
+ controller_ = controller.get();
+ service_ = base::MakeUnique<DownloadServiceImpl>(std::move(config),
+ std::move(controller));
+ }
+
+ protected:
+ test::MockController* controller_;
+ std::unique_ptr<DownloadServiceImpl> service_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadServiceImplTest);
+};
+
+} // namespace
+
+TEST_F(DownloadServiceImplTest, TestGetStatus) {
+ StartupStatus startup_status;
+ EXPECT_CALL(*controller_, GetState())
+ .WillOnce(Return(Controller::State::INITIALIZING))
+ .WillOnce(Return(Controller::State::READY))
+ .WillOnce(Return(Controller::State::UNAVAILABLE));
+
+ EXPECT_EQ(DownloadService::ServiceStatus::STARTING_UP, service_->GetStatus());
+ EXPECT_EQ(DownloadService::ServiceStatus::READY, service_->GetStatus());
+ EXPECT_EQ(DownloadService::ServiceStatus::UNAVAILABLE, service_->GetStatus());
+}
+
+TEST_F(DownloadServiceImplTest, TestApiPassThrough) {
+ DownloadParams params = test::BuildBasicDownloadParams();
+ // TODO(xingliu): Remove the limitation of upper case guid in
+ // |download_params|, see http://crbug.com/734818.
+ params.guid = base::ToUpperASCII(params.guid);
+ SchedulingParams scheduling_params;
+ scheduling_params.priority = SchedulingParams::Priority::UI;
+
+ EXPECT_CALL(*controller_, GetOwnerOfDownload(_))
+ .WillRepeatedly(Return(DownloadClient::TEST));
+
+ EXPECT_CALL(*controller_, StartDownload(_)).Times(0);
+ EXPECT_CALL(*controller_, PauseDownload(params.guid)).Times(0);
+ EXPECT_CALL(*controller_, ResumeDownload(params.guid)).Times(0);
+ EXPECT_CALL(*controller_, CancelDownload(params.guid)).Times(0);
+ EXPECT_CALL(*controller_, ChangeDownloadCriteria(params.guid, _)).Times(0);
+
+ {
+ base::HistogramTester histogram_tester;
+
+ service_->StartDownload(params);
+
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.Request.ClientAction",
+ static_cast<base::HistogramBase::Sample>(
+ stats::ServiceApiAction::START_DOWNLOAD),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.Request.ClientAction.__Test__",
+ static_cast<base::HistogramBase::Sample>(
+ stats::ServiceApiAction::START_DOWNLOAD),
+ 1);
+ histogram_tester.ExpectBucketCount(
+ "Download.Service.Request.BatteryRequirement",
+ static_cast<base::HistogramBase::Sample>(
+ params.scheduling_params.battery_requirements),
+ 1);
+
+ histogram_tester.ExpectTotalCount("Download.Service.Request.ClientAction",
+ 1);
+ histogram_tester.ExpectTotalCount(
+ "Download.Service.Request.ClientAction.__Test__", 1);
+ }
+ service_->PauseDownload(params.guid);
+ service_->ResumeDownload(params.guid);
+ service_->CancelDownload(params.guid);
+ service_->ChangeDownloadCriteria(params.guid, scheduling_params);
+ task_runner_->RunUntilIdle();
+
+ testing::Sequence seq1;
+ EXPECT_CALL(*controller_, StartDownload(_)).Times(1).InSequence(seq1);
+ EXPECT_CALL(*controller_, PauseDownload(params.guid))
+ .Times(1)
+ .InSequence(seq1);
+ EXPECT_CALL(*controller_, ResumeDownload(params.guid))
+ .Times(1)
+ .InSequence(seq1);
+ EXPECT_CALL(*controller_, CancelDownload(params.guid))
+ .Times(1)
+ .InSequence(seq1);
+ EXPECT_CALL(*controller_, ChangeDownloadCriteria(params.guid, _))
+ .Times(1)
+ .InSequence(seq1);
+
+ controller_->TriggerInitCompleted();
+ task_runner_->RunUntilIdle();
+
+ EXPECT_CALL(*controller_, PauseDownload(params.guid))
+ .Times(1)
+ .InSequence(seq1);
+ EXPECT_CALL(*controller_, ResumeDownload(params.guid))
+ .Times(1)
+ .InSequence(seq1);
+ service_->PauseDownload(params.guid);
+ service_->ResumeDownload(params.guid);
+ task_runner_->RunUntilIdle();
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/download_store.cc b/chromium/components/download/internal/download_store.cc
new file mode 100644
index 00000000000..877547846df
--- /dev/null
+++ b/chromium/components/download/internal/download_store.cc
@@ -0,0 +1,113 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/download_store.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/leveldb_proto/proto_database_impl.h"
+
+namespace download {
+
+namespace {
+
+const char kDatabaseClientName[] = "DownloadService";
+using KeyVector = std::vector<std::string>;
+using ProtoEntryVector = std::vector<protodb::Entry>;
+using KeyProtoEntryVector = std::vector<std::pair<std::string, protodb::Entry>>;
+
+} // namespace
+
+DownloadStore::DownloadStore(
+ const base::FilePath& database_dir,
+ std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db)
+ : db_(std::move(db)),
+ database_dir_(database_dir),
+ is_initialized_(false),
+ weak_factory_(this) {}
+
+DownloadStore::~DownloadStore() = default;
+
+bool DownloadStore::IsInitialized() {
+ return is_initialized_;
+}
+
+void DownloadStore::Initialize(InitCallback callback) {
+ DCHECK(!IsInitialized());
+ db_->InitWithOptions(
+ kDatabaseClientName, leveldb_proto::Options(database_dir_),
+ base::BindOnce(&DownloadStore::OnDatabaseInited,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void DownloadStore::HardRecover(StoreCallback callback) {
+ is_initialized_ = false;
+ db_->Destroy(base::BindOnce(&DownloadStore::OnDatabaseDestroyed,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void DownloadStore::OnDatabaseInited(InitCallback callback, bool success) {
+ if (!success) {
+ std::move(callback).Run(success, base::MakeUnique<std::vector<Entry>>());
+ return;
+ }
+
+ db_->LoadEntries(base::BindOnce(&DownloadStore::OnDatabaseLoaded,
+ weak_factory_.GetWeakPtr(),
+ std::move(callback)));
+}
+
+void DownloadStore::OnDatabaseLoaded(InitCallback callback,
+ bool success,
+ std::unique_ptr<ProtoEntryVector> protos) {
+ if (!success) {
+ std::move(callback).Run(success, base::MakeUnique<std::vector<Entry>>());
+ return;
+ }
+
+ auto entries = ProtoConversions::EntryVectorFromProto(std::move(protos));
+ is_initialized_ = true;
+ std::move(callback).Run(success, std::move(entries));
+}
+
+void DownloadStore::OnDatabaseDestroyed(StoreCallback callback, bool success) {
+ if (!success) {
+ std::move(callback).Run(success);
+ return;
+ }
+
+ db_->InitWithOptions(
+ kDatabaseClientName, leveldb_proto::Options(database_dir_),
+ base::BindOnce(&DownloadStore::OnDatabaseInitedAfterDestroy,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void DownloadStore::OnDatabaseInitedAfterDestroy(StoreCallback callback,
+ bool success) {
+ is_initialized_ = success;
+ std::move(callback).Run(success);
+}
+
+void DownloadStore::Update(const Entry& entry, StoreCallback callback) {
+ DCHECK(IsInitialized());
+ auto entries_to_save = base::MakeUnique<KeyProtoEntryVector>();
+ protodb::Entry proto = ProtoConversions::EntryToProto(entry);
+ entries_to_save->emplace_back(entry.guid, std::move(proto));
+ db_->UpdateEntries(std::move(entries_to_save), base::MakeUnique<KeyVector>(),
+ std::move(callback));
+}
+
+void DownloadStore::Remove(const std::string& guid, StoreCallback callback) {
+ DCHECK(IsInitialized());
+ auto keys_to_remove = base::MakeUnique<KeyVector>();
+ keys_to_remove->push_back(guid);
+ db_->UpdateEntries(base::MakeUnique<KeyProtoEntryVector>(),
+ std::move(keys_to_remove), std::move(callback));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/download_store.h b/chromium/components/download/internal/download_store.h
new file mode 100644
index 00000000000..adb397b6b3c
--- /dev/null
+++ b/chromium/components/download/internal/download_store.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/download/internal/store.h"
+#include "components/leveldb_proto/proto_database.h"
+
+namespace protodb {
+class Entry;
+} // namespace protodb
+
+namespace download {
+
+// DownloadStore provides a layer around a LevelDB proto database that persists
+// the download request, scheduling params and metadata to the disk. The data is
+// read during initialization and presented to the caller after converting to
+// Entry entries.
+class DownloadStore : public Store {
+ public:
+ DownloadStore(
+ const base::FilePath& database_dir,
+ std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db);
+ ~DownloadStore() override;
+
+ // Store implementation.
+ bool IsInitialized() override;
+ void Initialize(InitCallback callback) override;
+ void HardRecover(StoreCallback callback) override;
+ void Update(const Entry& entry, StoreCallback callback) override;
+ void Remove(const std::string& guid, StoreCallback callback) override;
+
+ private:
+ void OnDatabaseInited(InitCallback callback, bool success);
+ void OnDatabaseLoaded(InitCallback callback,
+ bool success,
+ std::unique_ptr<std::vector<protodb::Entry>> protos);
+ void OnDatabaseDestroyed(StoreCallback callback, bool success);
+ void OnDatabaseInitedAfterDestroy(StoreCallback callback, bool success);
+
+ std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db_;
+ base::FilePath database_dir_;
+ bool is_initialized_;
+
+ base::WeakPtrFactory<DownloadStore> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadStore);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_DOWNLOAD_STORE_H_
diff --git a/chromium/components/download/internal/download_store_unittest.cc b/chromium/components/download/internal/download_store_unittest.cc
new file mode 100644
index 00000000000..57b9997d554
--- /dev/null
+++ b/chromium/components/download/internal/download_store_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/download_store.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "base/optional.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "components/leveldb_proto/testing/fake_db.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace download {
+
+class DownloadStoreTest : public testing::Test {
+ public:
+ DownloadStoreTest() : db_(nullptr) {}
+
+ ~DownloadStoreTest() override = default;
+
+ void CreateDatabase() {
+ auto db = base::MakeUnique<leveldb_proto::test::FakeDB<protodb::Entry>>(
+ &db_entries_);
+ db_ = db.get();
+ store_.reset(new DownloadStore(
+ base::FilePath(FILE_PATH_LITERAL("/test/db/fakepath")), std::move(db)));
+ }
+
+ void InitCallback(std::vector<Entry>* loaded_entries,
+ bool success,
+ std::unique_ptr<std::vector<Entry>> entries) {
+ loaded_entries->swap(*entries);
+ }
+
+ void LoadCallback(std::vector<protodb::Entry>* loaded_entries,
+ bool success,
+ std::unique_ptr<std::vector<protodb::Entry>> entries) {
+ loaded_entries->swap(*entries);
+ }
+
+ void RecoverCallback(bool success) { hard_recover_result_ = success; }
+
+ MOCK_METHOD1(StoreCallback, void(bool));
+
+ void PrepopulateSampleEntries() {
+ Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ db_entries_.insert(
+ std::make_pair(item1.guid, ProtoConversions::EntryToProto(item1)));
+ db_entries_.insert(
+ std::make_pair(item2.guid, ProtoConversions::EntryToProto(item2)));
+ }
+
+ protected:
+ std::map<std::string, protodb::Entry> db_entries_;
+ leveldb_proto::test::FakeDB<protodb::Entry>* db_;
+ std::unique_ptr<DownloadStore> store_;
+ base::Optional<bool> hard_recover_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadStoreTest);
+};
+
+TEST_F(DownloadStoreTest, Initialize) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+ ASSERT_FALSE(store_->IsInitialized());
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_EQ(2u, preloaded_entries.size());
+}
+
+TEST_F(DownloadStoreTest, HardRecover) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+ ASSERT_FALSE(store_->IsInitialized());
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_EQ(2u, preloaded_entries.size());
+
+ store_->HardRecover(
+ base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
+
+ ASSERT_FALSE(store_->IsInitialized());
+
+ db_->DestroyCallback(true);
+ db_->InitCallback(true);
+
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_TRUE(hard_recover_result_.has_value());
+ ASSERT_TRUE(hard_recover_result_.value());
+}
+
+TEST_F(DownloadStoreTest, HardRecoverDestroyFails) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+ ASSERT_FALSE(store_->IsInitialized());
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_EQ(2u, preloaded_entries.size());
+
+ store_->HardRecover(
+ base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
+
+ ASSERT_FALSE(store_->IsInitialized());
+
+ db_->DestroyCallback(false);
+
+ ASSERT_FALSE(store_->IsInitialized());
+ ASSERT_TRUE(hard_recover_result_.has_value());
+ ASSERT_FALSE(hard_recover_result_.value());
+}
+
+TEST_F(DownloadStoreTest, HardRecoverInitFails) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+ ASSERT_FALSE(store_->IsInitialized());
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_EQ(2u, preloaded_entries.size());
+
+ store_->HardRecover(
+ base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
+
+ ASSERT_FALSE(store_->IsInitialized());
+
+ db_->DestroyCallback(true);
+ db_->InitCallback(false);
+
+ ASSERT_FALSE(store_->IsInitialized());
+ ASSERT_TRUE(hard_recover_result_.has_value());
+ ASSERT_FALSE(hard_recover_result_.value());
+}
+
+TEST_F(DownloadStoreTest, Update) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_EQ(2u, preloaded_entries.size());
+
+ Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ EXPECT_CALL(*this, StoreCallback(true)).Times(2);
+ store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(true);
+ store_->Update(item2, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(true);
+
+ // Query the database directly and check for the entry.
+ auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+ db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+ base::Unretained(this), protos.get()));
+ db_->LoadCallback(true);
+ ASSERT_EQ(4u, protos->size());
+ ASSERT_TRUE(test::CompareEntryList(
+ {preloaded_entries[0], preloaded_entries[1], item1, item2},
+ *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+TEST_F(DownloadStoreTest, Remove) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+ ASSERT_EQ(2u, preloaded_entries.size());
+
+ // Remove the entry.
+ EXPECT_CALL(*this, StoreCallback(true)).Times(1);
+ store_->Remove(
+ preloaded_entries[0].guid,
+ base::Bind(&DownloadStoreTest::StoreCallback, base::Unretained(this)));
+ db_->UpdateCallback(true);
+
+ // Query the database directly and check for the entry removed.
+ auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+ db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+ base::Unretained(this), protos.get()));
+ db_->LoadCallback(true);
+ ASSERT_EQ(1u, protos->size());
+ ASSERT_TRUE(test::CompareEntryList(
+ {preloaded_entries[1]},
+ *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+TEST_F(DownloadStoreTest, InitializeFailed) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(false);
+ ASSERT_FALSE(store_->IsInitialized());
+ ASSERT_TRUE(preloaded_entries.empty());
+}
+
+TEST_F(DownloadStoreTest, InitialLoadFailed) {
+ PrepopulateSampleEntries();
+ CreateDatabase();
+
+ std::vector<Entry> preloaded_entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &preloaded_entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(false);
+ ASSERT_FALSE(store_->IsInitialized());
+ ASSERT_TRUE(preloaded_entries.empty());
+}
+
+TEST_F(DownloadStoreTest, UnsuccessfulUpdateOrRemove) {
+ Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ CreateDatabase();
+
+ std::vector<Entry> entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+ ASSERT_TRUE(store_->IsInitialized());
+ ASSERT_TRUE(entries.empty());
+
+ // Update failed.
+ EXPECT_CALL(*this, StoreCallback(false)).Times(1);
+ store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(false);
+
+ // Remove failed.
+ EXPECT_CALL(*this, StoreCallback(false)).Times(1);
+ store_->Remove(item1.guid, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(false);
+}
+
+TEST_F(DownloadStoreTest, AddThenRemove) {
+ CreateDatabase();
+
+ std::vector<Entry> entries;
+ store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
+ base::Unretained(this), &entries));
+ db_->InitCallback(true);
+ db_->LoadCallback(true);
+ ASSERT_TRUE(entries.empty());
+
+ Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ EXPECT_CALL(*this, StoreCallback(true)).Times(2);
+ store_->Update(item1, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(true);
+ store_->Update(item2, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(true);
+
+ // Query the database directly and check for the entry.
+ auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+ db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+ base::Unretained(this), protos.get()));
+ db_->LoadCallback(true);
+ ASSERT_EQ(2u, protos->size());
+
+ // Remove the entry.
+ EXPECT_CALL(*this, StoreCallback(true)).Times(1);
+ store_->Remove(item1.guid, base::Bind(&DownloadStoreTest::StoreCallback,
+ base::Unretained(this)));
+ db_->UpdateCallback(true);
+
+ // Query the database directly and check for the entry removed.
+ protos->clear();
+ db_->LoadEntries(base::Bind(&DownloadStoreTest::LoadCallback,
+ base::Unretained(this), protos.get()));
+ db_->LoadCallback(true);
+ ASSERT_EQ(1u, protos->size());
+ ASSERT_TRUE(test::CompareEntryList(
+ {item2}, *ProtoConversions::EntryVectorFromProto(std::move(protos))));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/driver_entry.h b/chromium/components/download/internal/driver_entry.h
index a21a4312c9e..c7b41632c8d 100644
--- a/chromium/components/download/internal/driver_entry.h
+++ b/chromium/components/download/internal/driver_entry.h
@@ -6,8 +6,11 @@
#define COMPONENTS_DOWNLOAD_INTERNAL_DRIVER_ENTRY_H_
#include <string>
+#include <vector>
+#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
+#include "url/gurl.h"
namespace net {
class HttpResponseHeaders;
@@ -52,8 +55,22 @@ struct DriverEntry {
// http header is not presented.
uint64_t expected_total_size;
+ // The physical file path for the download. It can be different from the
+ // target file path requested while the file is downloading, as it may
+ // download to a temporary path. After completion, this would be set to the
+ // target file path.
+ base::FilePath current_file_path;
+
+ // Time the download was marked as complete, base::Time() if the download is
+ // not yet complete.
+ base::Time completion_time;
+
// The response headers for the most recent download request.
scoped_refptr<const net::HttpResponseHeaders> response_headers;
+
+ // The url chain of the download. Download may encounter redirects, and
+ // fetches the content from the last url in the chain.
+ std::vector<GURL> url_chain;
};
} // namespace download
diff --git a/chromium/components/download/internal/entry.cc b/chromium/components/download/internal/entry.cc
index 56d819c39d8..4df4757a0b0 100644
--- a/chromium/components/download/internal/entry.cc
+++ b/chromium/components/download/internal/entry.cc
@@ -6,8 +6,35 @@
namespace download {
-Entry::Entry() = default;
+Entry::Entry() : attempt_count(0) {}
Entry::Entry(const Entry& other) = default;
+
+Entry::Entry(const DownloadParams& params)
+ : client(params.client),
+ guid(params.guid),
+ create_time(base::Time::Now()),
+ scheduling_params(params.scheduling_params),
+ request_params(params.request_params),
+ attempt_count(0) {}
+
Entry::~Entry() = default;
+bool Entry::operator==(const Entry& other) const {
+ return client == other.client && guid == other.guid &&
+ scheduling_params.cancel_time == other.scheduling_params.cancel_time &&
+ scheduling_params.network_requirements ==
+ other.scheduling_params.network_requirements &&
+ scheduling_params.battery_requirements ==
+ other.scheduling_params.battery_requirements &&
+ scheduling_params.priority == other.scheduling_params.priority &&
+ request_params.url == other.request_params.url &&
+ request_params.method == other.request_params.method &&
+ request_params.request_headers.ToString() ==
+ other.request_params.request_headers.ToString() &&
+ state == other.state && target_file_path == other.target_file_path &&
+ create_time == other.create_time &&
+ completion_time == other.completion_time &&
+ attempt_count == other.attempt_count;
+}
+
} // namespace download
diff --git a/chromium/components/download/internal/entry.h b/chromium/components/download/internal/entry.h
index 9ba0c427527..20766317889 100644
--- a/chromium/components/download/internal/entry.h
+++ b/chromium/components/download/internal/entry.h
@@ -5,6 +5,7 @@
#ifndef COMPONENTS_DOWNLOAD_INTERNAL_ENTRY_H_
#define COMPONENTS_DOWNLOAD_INTERNAL_ENTRY_H_
+#include "base/time/time.h"
#include "components/download/public/client.h"
#include "components/download/public/clients.h"
#include "components/download/public/download_params.h"
@@ -32,25 +33,30 @@ struct Entry {
// be run until it is resumed by the Client.
PAUSED = 3,
- // The download is 'complete' by some definition of that term (could have
- // failed, could have succeeded, etc.). It is ready to have UMA logs saved.
+ // The download is 'complete' and successful. At this point we are leaving
+ // this entry around to make sure the files on disk are cleaned up.
COMPLETE = 4,
- // The download is finished. We are leaving this entry around to make sure
- // the files on disk are cleaned up.
- WATCHDOG = 5,
+ // The count of entries for the enum.
+ COUNT = 5,
};
Entry();
Entry(const Entry& other);
+ explicit Entry(const DownloadParams& params);
~Entry();
+ bool operator==(const Entry& other) const;
+
// The feature that is requesting this download.
DownloadClient client = DownloadClient::INVALID;
// A unique GUID that represents this download. See | base::GenerateGUID()|.
std::string guid;
+ // The time when the entry is created.
+ base::Time create_time;
+
// The parameters that determine under what device conditions this download
// will occur.
SchedulingParams scheduling_params;
@@ -61,6 +67,16 @@ struct Entry {
// The state of the download to help the scheduler and loggers make the right
// decisions about the download object.
State state = State::NEW;
+
+ // Target file path for this download.
+ base::FilePath target_file_path;
+
+ // Time the download was marked as complete, base::Time() if the download is
+ // not yet complete.
+ base::Time completion_time;
+
+ // Stores the number of retries for this download.
+ uint32_t attempt_count;
};
} // namespace download
diff --git a/chromium/components/download/internal/entry_utils.cc b/chromium/components/download/internal/entry_utils.cc
new file mode 100644
index 00000000000..35aa3ede353
--- /dev/null
+++ b/chromium/components/download/internal/entry_utils.cc
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/entry_utils.h"
+
+#include "components/download/internal/entry.h"
+
+namespace download {
+namespace util {
+
+uint32_t GetNumberOfEntriesForClient(DownloadClient client,
+ const std::vector<Entry*>& entries) {
+ uint32_t count = 0;
+ for (auto* entry : entries)
+ if (entry->client == client)
+ count++;
+
+ return count;
+}
+
+std::map<DownloadClient, std::vector<std::string>> MapEntriesToClients(
+ const std::set<DownloadClient>& clients,
+ const std::vector<Entry*>& entries,
+ const std::set<Entry::State>& ignored_states) {
+ std::map<DownloadClient, std::vector<std::string>> categorized;
+
+ for (auto* entry : entries) {
+ if (ignored_states.find(entry->state) != ignored_states.end())
+ continue;
+
+ DownloadClient client = entry->client;
+ if (clients.find(client) == clients.end())
+ client = DownloadClient::INVALID;
+
+ categorized[client].push_back(entry->guid);
+ }
+
+ return categorized;
+}
+
+Criteria GetSchedulingCriteria(const Model::EntryList& entries) {
+ Criteria criteria;
+ for (auto* const entry : entries) {
+ DCHECK(entry);
+ const SchedulingParams& scheduling_params = entry->scheduling_params;
+ if (scheduling_params.battery_requirements ==
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE) {
+ criteria.requires_battery_charging = false;
+ }
+ if (scheduling_params.network_requirements ==
+ SchedulingParams::NetworkRequirements::NONE) {
+ criteria.requires_unmetered_network = false;
+ }
+ }
+ return criteria;
+}
+
+bool EntryBetterThan(const Entry& lhs, const Entry& rhs) {
+ return lhs.scheduling_params.priority > rhs.scheduling_params.priority ||
+ (lhs.scheduling_params.priority == rhs.scheduling_params.priority &&
+ lhs.scheduling_params.cancel_time <
+ rhs.scheduling_params.cancel_time);
+}
+
+} // namespace util
+} // namespace download
diff --git a/chromium/components/download/internal/entry_utils.h b/chromium/components/download/internal/entry_utils.h
new file mode 100644
index 00000000000..780b49467fb
--- /dev/null
+++ b/chromium/components/download/internal/entry_utils.h
@@ -0,0 +1,49 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_ENTRY_UTILS_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_ENTRY_UTILS_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "components/download/internal/model.h"
+#include "components/download/internal/scheduler/device_status.h"
+#include "components/download/public/clients.h"
+
+namespace download {
+
+struct Entry;
+
+namespace util {
+
+// Helper method to return the number of Entry objects in |entries| are
+// associated with |client|.
+uint32_t GetNumberOfEntriesForClient(DownloadClient client,
+ const std::vector<Entry*>& entries);
+
+// Effectively runs a map reduce to turn a list of Entry objects into a map of
+// [Client Id] -> List of entries. Any Entry in |entries| that does not have a
+// matching DownloadClient in |clients| will be put in the
+// DownloadClient::INVALID bucket. Any Entry in |entries| with an Entry::State
+// that is in |ignored_states| will not be included.
+std::map<DownloadClient, std::vector<std::string>> MapEntriesToClients(
+ const std::set<DownloadClient>& clients,
+ const std::vector<Entry*>& entries,
+ const std::set<Entry::State>& ignored_states);
+
+// Gets the least strict scheduling criteria from |entries|, the criteria is
+// used to schedule platform background tasks.
+Criteria GetSchedulingCriteria(const Model::EntryList& entries);
+
+// Returns if |lhs| entry is a better candidate to be the next download than
+// |rhs| based on their priority and cancel time.
+bool EntryBetterThan(const Entry& lhs, const Entry& rhs);
+
+} // namespace util
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_ENTRY_UTILS_H_
diff --git a/chromium/components/download/internal/entry_utils_unittest.cc b/chromium/components/download/internal/entry_utils_unittest.cc
new file mode 100644
index 00000000000..472151dc50c
--- /dev/null
+++ b/chromium/components/download/internal/entry_utils_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/entry_utils.h"
+
+#include <algorithm>
+
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "components/download/public/clients.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace download {
+
+TEST(DownloadServiceEntryUtilsTest, TestGetNumberOfEntriesForClient_NoEntries) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+
+ std::vector<Entry*> entries = {&entry1, &entry2, &entry3};
+
+ EXPECT_EQ(
+ 0U, util::GetNumberOfEntriesForClient(DownloadClient::INVALID, entries));
+ EXPECT_EQ(3U,
+ util::GetNumberOfEntriesForClient(DownloadClient::TEST, entries));
+}
+
+TEST(DownloadServiceEntryUtilsTest, MapEntriesToClients) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+ Entry entry4 = test::BuildBasicEntry(Entry::State::COMPLETE);
+ Entry entry5 = test::BuildBasicEntry(Entry::State::AVAILABLE);
+
+ std::vector<Entry*> entries = {&entry1, &entry2, &entry3, &entry4, &entry5};
+ std::vector<std::string> expected_list = {
+ entry1.guid, entry2.guid, entry3.guid, entry4.guid, entry5.guid};
+ std::vector<std::string> expected_pruned_list = {entry1.guid, entry2.guid,
+ entry3.guid, entry5.guid};
+ // If DownloadClient::TEST isn't a valid Client, all of the associated entries
+ // should move to the DownloadClient::INVALID bucket.
+ auto mapped1 = util::MapEntriesToClients(std::set<DownloadClient>(), entries,
+ std::set<Entry::State>());
+ EXPECT_EQ(1U, mapped1.size());
+ EXPECT_NE(mapped1.end(), mapped1.find(DownloadClient::INVALID));
+ EXPECT_EQ(mapped1.end(), mapped1.find(DownloadClient::TEST));
+
+ auto list1 = mapped1.find(DownloadClient::INVALID)->second;
+ EXPECT_EQ(5U, list1.size());
+ EXPECT_TRUE(
+ std::equal(expected_list.begin(), expected_list.end(), list1.begin()));
+
+ // If DownloadClient::TEST is a valid Client, it should have the associated
+ // entries.
+ std::set<DownloadClient> clients = {DownloadClient::TEST};
+ auto mapped2 =
+ util::MapEntriesToClients(clients, entries, std::set<Entry::State>());
+ EXPECT_EQ(1U, mapped2.size());
+ EXPECT_NE(mapped2.end(), mapped2.find(DownloadClient::TEST));
+ EXPECT_EQ(mapped2.end(), mapped2.find(DownloadClient::INVALID));
+
+ auto list2 = mapped2.find(DownloadClient::TEST)->second;
+ EXPECT_EQ(5U, list2.size());
+ EXPECT_TRUE(
+ std::equal(expected_list.begin(), expected_list.end(), list2.begin()));
+
+ // If we are pruning entries with certain states, make sure those entries
+ // don't show up in the results.
+ std::set<Entry::State> ignored_states = {Entry::State::ACTIVE,
+ Entry::State::COMPLETE};
+ auto mapped3 = util::MapEntriesToClients(clients, entries, ignored_states);
+ EXPECT_EQ(1U, mapped3.size());
+ auto list3 = mapped3.find(DownloadClient::TEST)->second;
+ EXPECT_EQ(4U, list3.size());
+ EXPECT_TRUE(std::equal(expected_pruned_list.begin(),
+ expected_pruned_list.end(), list3.begin()));
+}
+
+TEST(DownloadServiceEntryUtilsTest, GetSchedulingCriteria) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ Entry entry3 = test::BuildBasicEntry();
+ Entry entry4 = test::BuildBasicEntry();
+
+ entry1.scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+ entry1.scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+
+ entry2.scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::NONE;
+ entry2.scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+
+ entry3.scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+ entry3.scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+
+ entry4.scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::NONE;
+ entry4.scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+
+ Model::EntryList list1 = {&entry1};
+ Model::EntryList list2 = {&entry1, &entry2};
+ Model::EntryList list3 = {&entry1, &entry3};
+ Model::EntryList list4 = {&entry1, &entry2, &entry3};
+ Model::EntryList list5 = {&entry1, &entry4};
+
+ EXPECT_EQ(Criteria(true, true), util::GetSchedulingCriteria(list1));
+ EXPECT_EQ(Criteria(true, false), util::GetSchedulingCriteria(list2));
+ EXPECT_EQ(Criteria(false, true), util::GetSchedulingCriteria(list3));
+ EXPECT_EQ(Criteria(false, false), util::GetSchedulingCriteria(list4));
+ EXPECT_EQ(Criteria(false, false), util::GetSchedulingCriteria(list5));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/file_monitor.h b/chromium/components/download/internal/file_monitor.h
new file mode 100644
index 00000000000..9df409c1e70
--- /dev/null
+++ b/chromium/components/download/internal/file_monitor.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "components/download/internal/model.h"
+#include "components/download/internal/stats.h"
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace download {
+
+struct DriverEntry;
+struct Entry;
+
+// An utility class containing various file cleanup methods.
+class FileMonitor {
+ public:
+ using InitCallback = base::Callback<void(bool)>;
+
+ // Creates the file directory for the downloads if it doesn't exist.
+ virtual void Initialize(const InitCallback& callback) = 0;
+
+ // Deletes the files in storage directory that are not related to any entries
+ // in either database.
+ virtual void DeleteUnknownFiles(
+ const Model::EntryList& known_entries,
+ const std::vector<DriverEntry>& known_driver_entries) = 0;
+
+ // Deletes the files for the database entries which have been completed and
+ // ready for cleanup. Returns the entries eligible for clean up.
+ virtual std::vector<Entry*> CleanupFilesForCompletedEntries(
+ const Model::EntryList& entries,
+ const base::Closure& completion_callback) = 0;
+
+ // Deletes a list of files and logs UMA.
+ virtual void DeleteFiles(const std::set<base::FilePath>& files_to_remove,
+ stats::FileCleanupReason reason) = 0;
+
+ // Deletes all files in the download service directory. This is a hard reset
+ // on this directory.
+ virtual void HardRecover(const InitCallback& callback) = 0;
+
+ virtual ~FileMonitor() = default;
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_H_
diff --git a/chromium/components/download/internal/file_monitor_impl.cc b/chromium/components/download/internal/file_monitor_impl.cc
new file mode 100644
index 00000000000..639db4b9f22
--- /dev/null
+++ b/chromium/components/download/internal/file_monitor_impl.cc
@@ -0,0 +1,219 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/file_monitor_impl.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace download {
+
+namespace {
+
+// Helper function to calculate total file size in a directory, the total
+// disk space and free disk space of the volume that contains that directory.
+// Returns false if failed to query disk space or total disk space is empty.
+bool CalculateDiskUtilization(const base::FilePath& file_dir,
+ int64_t& total_disk_space,
+ int64_t& free_disk_space,
+ int64_t& files_size) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ base::FileEnumerator file_enumerator(file_dir, false /* recursive */,
+ base::FileEnumerator::FILES);
+
+ int64_t size = 0;
+ // Compute the total size of all files in |file_dir|.
+ for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
+ path = file_enumerator.Next()) {
+ if (!base::GetFileSize(path, &size)) {
+ DVLOG(1) << "File size query failed.";
+ return false;
+ }
+ files_size += size;
+ }
+
+ // Disk space of the volume that |file_dir| belongs to.
+ total_disk_space = base::SysInfo::AmountOfTotalDiskSpace(file_dir);
+ free_disk_space = base::SysInfo::AmountOfFreeDiskSpace(file_dir);
+ if (total_disk_space == -1 || free_disk_space == -1) {
+ DVLOG(1) << "System disk space query failed.";
+ return false;
+ }
+
+ if (total_disk_space == 0) {
+ DVLOG(1) << "Empty total system disk space.";
+ return false;
+ }
+ return true;
+}
+
+// Creates the download directory if it doesn't exist.
+bool InitializeAndCreateDownloadDirectory(const base::FilePath& dir_path) {
+ // Create the download directory.
+ bool success = base::PathExists(dir_path);
+ if (!success) {
+ base::File::Error error = base::File::Error::FILE_OK;
+ success = base::CreateDirectoryAndGetError(dir_path, &error);
+ if (!success)
+ stats::LogsFileDirectoryCreationError(error);
+ }
+ // Records disk utilization histograms.
+ if (success) {
+ int64_t files_size = 0, total_disk_space = 0, free_disk_space = 0;
+ if (CalculateDiskUtilization(dir_path, total_disk_space, free_disk_space,
+ files_size)) {
+ stats::LogFileDirDiskUtilization(total_disk_space, free_disk_space,
+ files_size);
+ }
+ }
+
+ return success;
+}
+
+void GetFilesInDirectory(const base::FilePath& directory,
+ std::set<base::FilePath>& paths_out) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ base::FileEnumerator file_enumerator(directory, false /* recursive */,
+ base::FileEnumerator::FILES);
+
+ for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
+ path = file_enumerator.Next()) {
+ paths_out.insert(path);
+ }
+}
+
+void DeleteFilesOnFileThread(const std::set<base::FilePath>& paths,
+ stats::FileCleanupReason reason) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ int num_delete_attempted = 0;
+ int num_delete_failed = 0;
+ int num_delete_by_external = 0;
+ for (const base::FilePath& path : paths) {
+ if (!base::PathExists(path)) {
+ num_delete_by_external++;
+ continue;
+ }
+
+ num_delete_attempted++;
+ DCHECK(!base::DirectoryExists(path));
+
+ if (!base::DeleteFile(path, false /* recursive */)) {
+ num_delete_failed++;
+ }
+ }
+
+ stats::LogFileCleanupStatus(reason, num_delete_attempted, num_delete_failed,
+ num_delete_by_external);
+}
+
+void DeleteUnknownFilesOnFileThread(
+ const base::FilePath& directory,
+ const std::set<base::FilePath>& download_file_paths) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ std::set<base::FilePath> files_in_dir;
+ GetFilesInDirectory(directory, files_in_dir);
+
+ std::set<base::FilePath> files_to_remove =
+ base::STLSetDifference<std::set<base::FilePath>>(files_in_dir,
+ download_file_paths);
+ DeleteFilesOnFileThread(files_to_remove, stats::FileCleanupReason::UNKNOWN);
+}
+
+bool HardRecoverOnFileThread(const base::FilePath& directory) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ std::set<base::FilePath> files_in_dir;
+ GetFilesInDirectory(directory, files_in_dir);
+ DeleteFilesOnFileThread(files_in_dir,
+ stats::FileCleanupReason::HARD_RECOVERY);
+ return InitializeAndCreateDownloadDirectory(directory);
+}
+
+} // namespace
+
+FileMonitorImpl::FileMonitorImpl(
+ const base::FilePath& download_file_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& file_thread_task_runner,
+ base::TimeDelta file_keep_alive_time)
+ : download_file_dir_(download_file_dir),
+ file_keep_alive_time_(file_keep_alive_time),
+ file_thread_task_runner_(file_thread_task_runner),
+ weak_factory_(this) {}
+
+FileMonitorImpl::~FileMonitorImpl() = default;
+
+void FileMonitorImpl::Initialize(const InitCallback& callback) {
+ base::PostTaskAndReplyWithResult(
+ file_thread_task_runner_.get(), FROM_HERE,
+ base::Bind(&InitializeAndCreateDownloadDirectory, download_file_dir_),
+ callback);
+}
+
+void FileMonitorImpl::DeleteUnknownFiles(
+ const Model::EntryList& known_entries,
+ const std::vector<DriverEntry>& known_driver_entries) {
+ std::set<base::FilePath> download_file_paths;
+ for (Entry* entry : known_entries) {
+ download_file_paths.insert(entry->target_file_path);
+ }
+
+ for (const DriverEntry& driver_entry : known_driver_entries) {
+ download_file_paths.insert(driver_entry.current_file_path);
+ }
+
+ file_thread_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&DeleteUnknownFilesOnFileThread, download_file_dir_,
+ download_file_paths));
+}
+
+std::vector<Entry*> FileMonitorImpl::CleanupFilesForCompletedEntries(
+ const Model::EntryList& entries,
+ const base::Closure& completion_callback) {
+ std::vector<Entry*> entries_to_remove;
+ std::set<base::FilePath> files_to_remove;
+ for (auto* entry : entries) {
+ if (!ReadyForCleanup(entry))
+ continue;
+
+ entries_to_remove.push_back(entry);
+ files_to_remove.insert(entry->target_file_path);
+
+ // TODO(xingliu): Consider logs life time after the file being deleted on
+ // the file thread.
+ stats::LogFileLifeTime(base::Time::Now() - entry->completion_time);
+ }
+
+ file_thread_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&DeleteFilesOnFileThread, files_to_remove,
+ stats::FileCleanupReason::TIMEOUT),
+ completion_callback);
+ return entries_to_remove;
+}
+
+void FileMonitorImpl::DeleteFiles(
+ const std::set<base::FilePath>& files_to_remove,
+ stats::FileCleanupReason reason) {
+ file_thread_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&DeleteFilesOnFileThread, files_to_remove, reason));
+}
+
+void FileMonitorImpl::HardRecover(const InitCallback& callback) {
+ base::PostTaskAndReplyWithResult(
+ file_thread_task_runner_.get(), FROM_HERE,
+ base::Bind(&HardRecoverOnFileThread, download_file_dir_), callback);
+}
+
+bool FileMonitorImpl::ReadyForCleanup(const Entry* entry) {
+ return entry->state == Entry::State::COMPLETE &&
+ (base::Time::Now() - entry->completion_time) > file_keep_alive_time_;
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/file_monitor_impl.h b/chromium/components/download/internal/file_monitor_impl.h
new file mode 100644
index 00000000000..f0e5b55ad51
--- /dev/null
+++ b/chromium/components/download/internal/file_monitor_impl.h
@@ -0,0 +1,63 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_IMPL_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_IMPL_H_
+
+#include "components/download/internal/file_monitor.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "components/download/internal/driver_entry.h"
+#include "components/download/internal/model.h"
+#include "components/download/internal/stats.h"
+
+namespace download {
+
+struct Entry;
+
+// An utility class containing various file cleanup methods.
+class FileMonitorImpl : public FileMonitor {
+ public:
+ FileMonitorImpl(
+ const base::FilePath& download_file_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& file_thread_task_runner,
+ base::TimeDelta file_keep_alive_time);
+ ~FileMonitorImpl() override;
+
+ // FileMonitor implementation.
+ void Initialize(const InitCallback& callback) override;
+ void DeleteUnknownFiles(
+ const Model::EntryList& known_entries,
+ const std::vector<DriverEntry>& known_driver_entries) override;
+ std::vector<Entry*> CleanupFilesForCompletedEntries(
+ const Model::EntryList& entries,
+ const base::Closure& completion_callback) override;
+ void DeleteFiles(const std::set<base::FilePath>& files_to_remove,
+ stats::FileCleanupReason reason) override;
+ void HardRecover(const InitCallback& callback) override;
+
+ private:
+ bool ReadyForCleanup(const Entry* entry);
+
+ const base::FilePath download_file_dir_;
+ const base::TimeDelta file_keep_alive_time_;
+
+ scoped_refptr<base::SequencedTaskRunner> file_thread_task_runner_;
+ base::WeakPtrFactory<FileMonitorImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMonitorImpl);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_FILE_MONITOR_IMPL_H_
diff --git a/chromium/components/download/internal/file_monitor_unittest.cc b/chromium/components/download/internal/file_monitor_unittest.cc
new file mode 100644
index 00000000000..d668872c802
--- /dev/null
+++ b/chromium/components/download/internal/file_monitor_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "base/optional.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/download/internal/driver_entry.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/file_monitor_impl.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace download {
+
+class FileMonitorTest : public testing::Test {
+ public:
+ FileMonitorTest()
+ : task_runner_(new base::TestSimpleTaskRunner),
+ handle_(task_runner_),
+ completion_callback_called_(false) {
+ EXPECT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+ download_dir_ = scoped_temp_dir_.GetPath();
+ base::TimeDelta keep_alive_time = base::TimeDelta::FromHours(12);
+ monitor_ = base::MakeUnique<FileMonitorImpl>(download_dir_, task_runner_,
+ keep_alive_time);
+ }
+ ~FileMonitorTest() override = default;
+
+ void HardRecoveryResponse(bool result);
+ void CompletionCallback() { completion_callback_called_ = true; }
+
+ protected:
+ base::FilePath CreateTemporaryFile(std::string file_name);
+
+ base::ScopedTempDir scoped_temp_dir_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle handle_;
+ base::FilePath download_dir_;
+ bool completion_callback_called_;
+ std::unique_ptr<FileMonitor> monitor_;
+
+ base::Optional<bool> hard_recovery_result_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileMonitorTest);
+};
+
+base::FilePath FileMonitorTest::CreateTemporaryFile(std::string file_name) {
+ base::FilePath file_path = download_dir_.AppendASCII(file_name);
+ base::File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ EXPECT_TRUE(file.IsValid());
+ file.Close();
+
+ return file_path;
+}
+
+void FileMonitorTest::HardRecoveryResponse(bool result) {
+ hard_recovery_result_ = result;
+}
+
+TEST_F(FileMonitorTest, TestDeleteUnknownFiles) {
+ Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ entry1.target_file_path = CreateTemporaryFile(entry1.guid);
+
+ Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ entry2.target_file_path = CreateTemporaryFile(entry2.guid);
+
+ DriverEntry driver_entry1;
+ driver_entry1.guid = entry1.guid;
+ driver_entry1.current_file_path = entry1.target_file_path;
+
+ DriverEntry driver_entry2;
+ driver_entry2.guid = base::GenerateGUID();
+ driver_entry2.current_file_path = CreateTemporaryFile(driver_entry2.guid);
+
+ base::FilePath temp_file1 = CreateTemporaryFile("temp1");
+ base::FilePath temp_file2 = CreateTemporaryFile("temp2");
+
+ auto check_file_existence = [&](bool e1, bool e2, bool de1, bool de2, bool t1,
+ bool t2) {
+ EXPECT_EQ(e1, base::PathExists(entry1.target_file_path));
+ EXPECT_EQ(e2, base::PathExists(entry2.target_file_path));
+ EXPECT_EQ(de1, base::PathExists(driver_entry1.current_file_path));
+ EXPECT_EQ(de2, base::PathExists(driver_entry2.current_file_path));
+ EXPECT_EQ(t1, base::PathExists(temp_file1));
+ EXPECT_EQ(t2, base::PathExists(temp_file2));
+ };
+
+ check_file_existence(true, true, true, true, true, true);
+
+ std::vector<Entry*> entries = {&entry1, &entry2};
+ std::vector<DriverEntry> driver_entries = {driver_entry1, driver_entry2};
+
+ monitor_->DeleteUnknownFiles(entries, driver_entries);
+ task_runner_->RunUntilIdle();
+ check_file_existence(true, true, true, true, false, false);
+
+ entries = {&entry2};
+ driver_entries = {driver_entry1, driver_entry2};
+ monitor_->DeleteUnknownFiles(entries, driver_entries);
+ task_runner_->RunUntilIdle();
+ check_file_existence(true, true, true, true, false, false);
+
+ entries = {&entry2};
+ driver_entries = {driver_entry2};
+ monitor_->DeleteUnknownFiles(entries, driver_entries);
+ task_runner_->RunUntilIdle();
+ check_file_existence(false, true, false, true, false, false);
+
+ entries.clear();
+ driver_entries.clear();
+ monitor_->DeleteUnknownFiles(entries, driver_entries);
+ task_runner_->RunUntilIdle();
+ check_file_existence(false, false, false, false, false, false);
+}
+
+TEST_F(FileMonitorTest, TestCleanupFilesForCompletedEntries) {
+ Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ entry1.state = Entry::State::COMPLETE;
+ entry1.completion_time = base::Time::Now() - base::TimeDelta::FromHours(20);
+ EXPECT_TRUE(
+ base::CreateTemporaryFileInDir(download_dir_, &entry1.target_file_path));
+
+ Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ entry2.state = Entry::State::ACTIVE;
+ EXPECT_TRUE(
+ base::CreateTemporaryFileInDir(download_dir_, &entry2.target_file_path));
+
+ std::vector<Entry*> entries = {&entry1, &entry2};
+ std::vector<Entry*> entries_to_remove =
+ monitor_->CleanupFilesForCompletedEntries(
+ entries, base::Bind(&FileMonitorTest::CompletionCallback,
+ base::Unretained(this)));
+ task_runner_->RunUntilIdle();
+
+ EXPECT_EQ(1u, entries_to_remove.size());
+ EXPECT_FALSE(base::PathExists(entry1.target_file_path));
+ EXPECT_TRUE(base::PathExists(entry2.target_file_path));
+ EXPECT_TRUE(completion_callback_called_);
+}
+
+TEST_F(FileMonitorTest, TestHardRecovery) {
+ base::FilePath temp_file1 = CreateTemporaryFile("temp1");
+ base::FilePath temp_file2 = CreateTemporaryFile("temp2");
+
+ auto callback = base::Bind(&FileMonitorTest::HardRecoveryResponse,
+ base::Unretained(this));
+
+ EXPECT_TRUE(base::PathExists(temp_file1));
+ EXPECT_TRUE(base::PathExists(temp_file2));
+
+ monitor_->HardRecover(callback);
+ task_runner_->RunUntilIdle();
+
+ EXPECT_TRUE(hard_recovery_result_.has_value());
+ EXPECT_TRUE(hard_recovery_result_.value());
+ EXPECT_FALSE(base::PathExists(temp_file1));
+ EXPECT_FALSE(base::PathExists(temp_file2));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/model.h b/chromium/components/download/internal/model.h
index ce5de88e450..c1b2ed0948f 100644
--- a/chromium/components/download/internal/model.h
+++ b/chromium/components/download/internal/model.h
@@ -9,11 +9,11 @@
#include <string>
#include <vector>
+#include "components/download/internal/entry.h"
#include "components/download/public/clients.h"
namespace download {
-struct Entry;
class Store;
// The model that contains a runtime representation of Entry entries. Any
@@ -30,12 +30,12 @@ class Model {
// |success| is |false|, initialization of the Model and/or the underlying
// Store failed. Initialization of the Model is complete after this
// callback. If |success| is true it can be accessed now.
- virtual void OnInitialized(bool success) = 0;
+ virtual void OnModelReady(bool success) = 0;
- // Called asynchronously in response to a Model::Destroy call. If |success|
- // is |false|, destruction of the Model and/or the underlying Store failed.
- // Destruction of the Model is effectively complete after this callback.
- virtual void OnDestroyed(bool success) = 0;
+ // Called asynchronously in response to a Model::HardRecover call. If
+ // |success| is |false|, recovery of the Model and/or the underlying Store
+ // failed. After this call there should be no entries stored in this Model.
+ virtual void OnModelHardRecoverComplete(bool success) = 0;
// Called when an Entry addition is complete. |success| determines whether
// or not the entry has been successfully persisted to the Store.
@@ -62,10 +62,11 @@ class Model {
// Initializes the Model. Client::OnInitialized() will be called in response.
// The Model can be used after that call.
- virtual void Initialize() = 0;
+ virtual void Initialize(Client* client) = 0;
- // Destroys the Model. Client::OnDestroyed() will be called in response.
- virtual void Destroy() = 0;
+ // Deletes and attempts to re-initialize the Store.
+ // Client::OnHardRecoveryComplete() will be called in response asynchronously.
+ virtual void HardRecover() = 0;
// Adds |entry| to this Model and attempts to write |entry| to the Store.
// Client::OnItemAdded() will be called in response asynchronously.
diff --git a/chromium/components/download/internal/model_impl.cc b/chromium/components/download/internal/model_impl.cc
index f061b5de07d..9a62961ab70 100644
--- a/chromium/components/download/internal/model_impl.cc
+++ b/chromium/components/download/internal/model_impl.cc
@@ -4,29 +4,37 @@
#include "components/download/internal/model_impl.h"
+#include <map>
+
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "components/download/internal/entry.h"
+#include "components/download/internal/stats.h"
namespace download {
-ModelImpl::ModelImpl(Client* client, std::unique_ptr<Store> store)
- : client_(client), store_(std::move(store)), weak_ptr_factory_(this) {
- DCHECK(client_);
+ModelImpl::ModelImpl(std::unique_ptr<Store> store)
+ : client_(nullptr), store_(std::move(store)), weak_ptr_factory_(this) {
DCHECK(store_);
}
ModelImpl::~ModelImpl() = default;
-void ModelImpl::Initialize() {
+void ModelImpl::Initialize(Client* client) {
+ DCHECK(!client_);
+ client_ = client;
+ DCHECK(client_);
+
DCHECK(!store_->IsInitialized());
store_->Initialize(base::Bind(&ModelImpl::OnInitializedFinished,
weak_ptr_factory_.GetWeakPtr()));
}
-void ModelImpl::Destroy() {
- store_->Destroy(base::Bind(&ModelImpl::OnDestroyFinished,
- weak_ptr_factory_.GetWeakPtr()));
+void ModelImpl::HardRecover() {
+ entries_.clear();
+
+ store_->HardRecover(base::BindOnce(&ModelImpl::OnHardRecoverFinished,
+ weak_ptr_factory_.GetWeakPtr()));
}
void ModelImpl::Add(const Entry& entry) {
@@ -35,19 +43,20 @@ void ModelImpl::Add(const Entry& entry) {
entries_.emplace(entry.guid, base::MakeUnique<Entry>(entry));
- store_->Update(entry, base::Bind(&ModelImpl::OnAddFinished,
- weak_ptr_factory_.GetWeakPtr(), entry.client,
- entry.guid));
+ store_->Update(entry, base::BindOnce(&ModelImpl::OnAddFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ entry.client, entry.guid));
}
void ModelImpl::Update(const Entry& entry) {
DCHECK(store_->IsInitialized());
DCHECK(entries_.find(entry.guid) != entries_.end());
- entries_[entry.guid] = base::MakeUnique<Entry>(entry);
- store_->Update(entry, base::Bind(&ModelImpl::OnUpdateFinished,
- weak_ptr_factory_.GetWeakPtr(), entry.client,
- entry.guid));
+ *entries_[entry.guid] = entry;
+
+ store_->Update(entry, base::BindOnce(&ModelImpl::OnUpdateFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ entry.client, entry.guid));
}
void ModelImpl::Remove(const std::string& guid) {
@@ -56,11 +65,14 @@ void ModelImpl::Remove(const std::string& guid) {
const auto& it = entries_.find(guid);
DCHECK(it != entries_.end());
+ // Pull out a separate guid and a DownloadClient so that when we destroy the
+ // entry we don't destroy the std::string that is backing the guid.
+ std::string standalone_guid = guid;
DownloadClient client = it->second->client;
entries_.erase(it);
- store_->Remove(guid,
- base::Bind(&ModelImpl::OnRemoveFinished,
- weak_ptr_factory_.GetWeakPtr(), client, guid));
+ store_->Remove(standalone_guid, base::BindOnce(&ModelImpl::OnRemoveFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ client, standalone_guid));
}
Entry* ModelImpl::Get(const std::string& guid) {
@@ -79,33 +91,41 @@ Model::EntryList ModelImpl::PeekEntries() {
void ModelImpl::OnInitializedFinished(
bool success,
std::unique_ptr<std::vector<Entry>> entries) {
+ stats::LogModelOperationResult(stats::ModelAction::INITIALIZE, success);
+
if (!success) {
- client_->OnInitialized(false);
+ client_->OnModelReady(false);
return;
}
- for (const auto& entry : *entries)
+ std::map<Entry::State, uint32_t> entries_count;
+ for (const auto& entry : *entries) {
+ entries_count[entry.state]++;
entries_.emplace(entry.guid, base::MakeUnique<Entry>(entry));
+ }
- client_->OnInitialized(true);
+ stats::LogEntries(entries_count);
+ client_->OnModelReady(true);
}
-void ModelImpl::OnDestroyFinished(bool success) {
- store_.reset();
- entries_.clear();
- client_->OnDestroyed(success);
+void ModelImpl::OnHardRecoverFinished(bool success) {
+ client_->OnModelHardRecoverComplete(success);
}
void ModelImpl::OnAddFinished(DownloadClient client,
const std::string& guid,
bool success) {
+ stats::LogModelOperationResult(stats::ModelAction::ADD, success);
+
// Don't notify the Client if the entry was already removed.
- if (entries_.find(guid) == entries_.end())
+ auto it = entries_.find(guid);
+ if (it == entries_.end())
return;
// Remove the entry from the map if the add failed.
- if (!success)
- entries_.erase(guid);
+ if (!success) {
+ entries_.erase(it);
+ }
client_->OnItemAdded(success, client, guid);
}
@@ -113,6 +133,8 @@ void ModelImpl::OnAddFinished(DownloadClient client,
void ModelImpl::OnUpdateFinished(DownloadClient client,
const std::string& guid,
bool success) {
+ stats::LogModelOperationResult(stats::ModelAction::UPDATE, success);
+
// Don't notify the Client if the entry was already removed.
if (entries_.find(guid) == entries_.end())
return;
@@ -123,6 +145,8 @@ void ModelImpl::OnUpdateFinished(DownloadClient client,
void ModelImpl::OnRemoveFinished(DownloadClient client,
const std::string& guid,
bool success) {
+ stats::LogModelOperationResult(stats::ModelAction::REMOVE, success);
+
DCHECK(entries_.find(guid) == entries_.end());
client_->OnItemRemoved(success, client, guid);
}
diff --git a/chromium/components/download/internal/model_impl.h b/chromium/components/download/internal/model_impl.h
index 8c785019566..b2809f697f0 100644
--- a/chromium/components/download/internal/model_impl.h
+++ b/chromium/components/download/internal/model_impl.h
@@ -23,12 +23,12 @@ struct Entry;
// The internal implementation of Model.
class ModelImpl : public Model {
public:
- ModelImpl(Client* client, std::unique_ptr<Store> store);
+ ModelImpl(std::unique_ptr<Store> store);
~ModelImpl() override;
// Model implementation.
- void Initialize() override;
- void Destroy() override;
+ void Initialize(Client* client) override;
+ void HardRecover() override;
void Add(const Entry& entry) override;
void Update(const Entry& entry) override;
void Remove(const std::string& guid) override;
@@ -40,7 +40,7 @@ class ModelImpl : public Model {
void OnInitializedFinished(bool success,
std::unique_ptr<std::vector<Entry>> entries);
- void OnDestroyFinished(bool success);
+ void OnHardRecoverFinished(bool success);
void OnAddFinished(DownloadClient client,
const std::string& guid,
bool success);
@@ -53,7 +53,7 @@ class ModelImpl : public Model {
// The external Model::Client reference that will receive all interesting
// Model notifications.
- Client* const client_;
+ Client* client_;
// The backing Store that is responsible for saving and loading the
// persisted entries.
diff --git a/chromium/components/download/internal/model_impl_unittest.cc b/chromium/components/download/internal/model_impl_unittest.cc
index 19a78391702..624715f7d25 100644
--- a/chromium/components/download/internal/model_impl_unittest.cc
+++ b/chromium/components/download/internal/model_impl_unittest.cc
@@ -11,7 +11,9 @@
#include "base/guid.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/test/histogram_tester.h"
#include "components/download/internal/entry.h"
+#include "components/download/internal/stats.h"
#include "components/download/internal/test/entry_utils.h"
#include "components/download/internal/test/mock_model_client.h"
#include "components/download/internal/test/test_store.h"
@@ -35,7 +37,7 @@ class DownloadServiceModelImplTest : public testing::Test {
void SetUp() override {
auto store = base::MakeUnique<test::TestStore>();
store_ = store.get();
- model_ = base::MakeUnique<ModelImpl>(&client_, std::move(store));
+ model_ = base::MakeUnique<ModelImpl>(std::move(store));
}
protected:
@@ -51,85 +53,141 @@ class DownloadServiceModelImplTest : public testing::Test {
TEST_F(DownloadServiceModelImplTest, SuccessfulLifecycle) {
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
- EXPECT_CALL(client_, OnDestroyed(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
-
- model_->Destroy();
- EXPECT_TRUE(store_->destroy_called());
- store_->TriggerDestroy(true);
}
TEST_F(DownloadServiceModelImplTest, SuccessfulInitWithEntries) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
- Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
std::vector<Entry> entries = {entry1, entry2};
+ base::HistogramTester histogram_tester;
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
+
+ // Verify histograms.
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records", 2, 1);
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records.New", 2, 1);
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Available", 0,
+ 1);
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Active", 0,
+ 1);
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Paused", 0,
+ 1);
+ histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Complete", 0,
+ 1);
}
TEST_F(DownloadServiceModelImplTest, BadInit) {
- EXPECT_CALL(client_, OnInitialized(false)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(false)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
}
-TEST_F(DownloadServiceModelImplTest, BadDestroy) {
- InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
- EXPECT_CALL(client_, OnDestroyed(false)).Times(1);
+TEST_F(DownloadServiceModelImplTest, HardRecoverGoodModel) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ std::vector<Entry> entries = {entry1, entry2};
- model_->Initialize();
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
+
+ model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
- store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+
+ EXPECT_CALL(client_, OnModelHardRecoverComplete(true));
- model_->Destroy();
- EXPECT_TRUE(store_->destroy_called());
- store_->TriggerDestroy(false);
+ model_->HardRecover();
+ store_->TriggerHardRecover(true);
+ EXPECT_TRUE(model_->PeekEntries().empty());
+}
+
+TEST_F(DownloadServiceModelImplTest, HardRecoverBadModel) {
+ EXPECT_CALL(client_, OnModelReady(false)).Times(1);
+
+ model_->Initialize(&client_);
+ EXPECT_TRUE(store_->init_called());
+ store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
+
+ EXPECT_CALL(client_, OnModelHardRecoverComplete(true));
+
+ model_->HardRecover();
+ store_->TriggerHardRecover(true);
+ EXPECT_TRUE(model_->PeekEntries().empty());
+}
+
+TEST_F(DownloadServiceModelImplTest, HardRecoverFailsGoodModel) {
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
+ std::vector<Entry> entries = {entry1, entry2};
+
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
+
+ model_->Initialize(&client_);
+ EXPECT_TRUE(store_->init_called());
+ store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+
+ EXPECT_CALL(client_, OnModelHardRecoverComplete(false));
+
+ model_->HardRecover();
+ store_->TriggerHardRecover(false);
+ EXPECT_TRUE(model_->PeekEntries().empty());
+}
+
+TEST_F(DownloadServiceModelImplTest, HardRecoverFailsBadModel) {
+ EXPECT_CALL(client_, OnModelReady(false)).Times(1);
+
+ model_->Initialize(&client_);
+ EXPECT_TRUE(store_->init_called());
+ store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
+
+ EXPECT_CALL(client_, OnModelHardRecoverComplete(false));
+
+ model_->HardRecover();
+ store_->TriggerHardRecover(false);
+ EXPECT_TRUE(model_->PeekEntries().empty());
}
TEST_F(DownloadServiceModelImplTest, Add) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
- Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
EXPECT_CALL(client_, OnItemAdded(true, entry1.client, entry1.guid)).Times(1);
EXPECT_CALL(client_, OnItemAdded(false, entry2.client, entry2.guid)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
model_->Add(entry1);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
- EXPECT_TRUE(
- test::SuperficialEntryCompare(&entry1, store_->LastUpdatedEntry()));
+ EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry1, store_->LastUpdatedEntry()));
store_->TriggerUpdate(true);
model_->Add(entry2);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
- EXPECT_TRUE(
- test::SuperficialEntryCompare(&entry2, store_->LastUpdatedEntry()));
+ EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry()));
store_->TriggerUpdate(false);
EXPECT_EQ(nullptr, model_->Get(entry2.guid));
}
TEST_F(DownloadServiceModelImplTest, Update) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
Entry entry2(entry1);
entry2.state = Entry::State::AVAILABLE;
@@ -140,43 +198,51 @@ TEST_F(DownloadServiceModelImplTest, Update) {
std::vector<Entry> entries = {entry1};
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
EXPECT_CALL(client_, OnItemUpdated(true, entry1.client, entry1.guid))
- .Times(1);
+ .Times(2);
EXPECT_CALL(client_, OnItemUpdated(false, entry1.client, entry1.guid))
.Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
+ std::vector<Entry*> entries_pointers = model_->PeekEntries();
+ // Update with a different object.
model_->Update(entry2);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
- EXPECT_TRUE(
- test::SuperficialEntryCompare(&entry2, store_->LastUpdatedEntry()));
+ EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry()));
+ store_->TriggerUpdate(true);
+
+ // Update with the same object.
+ Entry* entry = model_->Get(entry1.guid);
+ entry->state = Entry::State::NEW;
+ model_->Update(*entry);
store_->TriggerUpdate(true);
+ // Peek entries should return the same set of pointers.
+ EXPECT_TRUE(test::CompareEntryList(entries_pointers, model_->PeekEntries()));
model_->Update(entry3);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry3, model_->Get(entry3.guid)));
- EXPECT_TRUE(
- test::SuperficialEntryCompare(&entry3, store_->LastUpdatedEntry()));
+ EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry3, store_->LastUpdatedEntry()));
store_->TriggerUpdate(false);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry3, model_->Get(entry3.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid)));
}
TEST_F(DownloadServiceModelImplTest, Remove) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
- Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
std::vector<Entry> entries = {entry1, entry2};
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
EXPECT_CALL(client_, OnItemRemoved(true, entry1.client, entry1.guid))
.Times(1);
EXPECT_CALL(client_, OnItemRemoved(false, entry2.client, entry2.guid))
.Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
model_->Remove(entry1.guid);
@@ -191,50 +257,49 @@ TEST_F(DownloadServiceModelImplTest, Remove) {
}
TEST_F(DownloadServiceModelImplTest, Get) {
- Entry entry = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry = test::BuildBasicEntry();
std::vector<Entry> entries = {entry};
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry, model_->Get(entry.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid)));
EXPECT_EQ(nullptr, model_->Get(base::GenerateGUID()));
}
TEST_F(DownloadServiceModelImplTest, PeekEntries) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
- Entry entry2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
+ Entry entry2 = test::BuildBasicEntry();
std::vector<Entry> entries = {entry1, entry2};
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
std::vector<Entry*> expected_peek = {&entry1, &entry2};
- EXPECT_TRUE(
- test::SuperficialEntryListCompare(expected_peek, model_->PeekEntries()));
+ EXPECT_TRUE(test::CompareEntryList(expected_peek, model_->PeekEntries()));
}
TEST_F(DownloadServiceModelImplTest, TestRemoveAfterAdd) {
- Entry entry = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry = test::BuildBasicEntry();
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
EXPECT_CALL(client_, OnItemAdded(_, _, _)).Times(0);
EXPECT_CALL(client_, OnItemRemoved(true, entry.client, entry.guid)).Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>());
model_->Add(entry);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry, model_->Get(entry.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid)));
model_->Remove(entry.guid);
EXPECT_EQ(nullptr, model_->Get(entry.guid));
@@ -244,7 +309,7 @@ TEST_F(DownloadServiceModelImplTest, TestRemoveAfterAdd) {
}
TEST_F(DownloadServiceModelImplTest, TestRemoveAfterUpdate) {
- Entry entry1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry entry1 = test::BuildBasicEntry();
Entry entry2(entry1);
entry2.state = Entry::State::AVAILABLE;
@@ -252,17 +317,17 @@ TEST_F(DownloadServiceModelImplTest, TestRemoveAfterUpdate) {
std::vector<Entry> entries = {entry1};
InSequence sequence;
- EXPECT_CALL(client_, OnInitialized(true)).Times(1);
+ EXPECT_CALL(client_, OnModelReady(true)).Times(1);
EXPECT_CALL(client_, OnItemUpdated(_, _, _)).Times(0);
EXPECT_CALL(client_, OnItemRemoved(true, entry1.client, entry1.guid))
.Times(1);
- model_->Initialize();
+ model_->Initialize(&client_);
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry1, model_->Get(entry1.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid)));
model_->Update(entry2);
- EXPECT_TRUE(test::SuperficialEntryCompare(&entry2, model_->Get(entry2.guid)));
+ EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid)));
model_->Remove(entry2.guid);
EXPECT_EQ(nullptr, model_->Get(entry2.guid));
diff --git a/chromium/components/download/internal/noop_store.cc b/chromium/components/download/internal/noop_store.cc
deleted file mode 100644
index 42785cbc5b1..00000000000
--- a/chromium/components/download/internal/noop_store.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/internal/noop_store.h"
-
-#include "base/bind.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "components/download/internal/entry.h"
-
-namespace download {
-
-NoopStore::NoopStore() : initialized_(false), weak_ptr_factory_(this) {}
-
-NoopStore::~NoopStore() = default;
-
-bool NoopStore::IsInitialized() {
- return initialized_;
-}
-
-void NoopStore::Initialize(InitCallback callback) {
- DCHECK(!IsInitialized());
-
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(&NoopStore::OnInitFinished, weak_ptr_factory_.GetWeakPtr(),
- std::move(callback)));
-}
-
-void NoopStore::Destroy(StoreCallback callback) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
-}
-
-void NoopStore::Update(const Entry& entry, StoreCallback callback) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
-}
-
-void NoopStore::Remove(const std::string& guid, StoreCallback callback) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
-}
-
-void NoopStore::OnInitFinished(InitCallback callback) {
- initialized_ = true;
-
- std::unique_ptr<std::vector<Entry>> entries =
- base::MakeUnique<std::vector<Entry>>();
- std::move(callback).Run(true /** success */, std::move(entries));
-}
-
-} // namespace download
diff --git a/chromium/components/download/internal/noop_store.h b/chromium/components/download/internal/noop_store.h
deleted file mode 100644
index 4bad9ed2e13..00000000000
--- a/chromium/components/download/internal/noop_store.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
-#define COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
-
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "components/download/internal/store.h"
-
-namespace download {
-
-struct Entry;
-
-// A Store implementation that doesn't do anything but honors the interface
-// requirements.
-// TODO(dtrainor, shaktisahu): Remove this if it's no longer necessary after the
-// real Store implementation is added.
-class NoopStore : public Store {
- public:
- NoopStore();
- ~NoopStore() override;
-
- // Store implementation.
- bool IsInitialized() override;
- void Initialize(InitCallback callback) override;
- void Destroy(StoreCallback callback) override;
- void Update(const Entry& entry, StoreCallback callback) override;
- void Remove(const std::string& guid, StoreCallback callback) override;
-
- private:
- void OnInitFinished(InitCallback callback);
-
- // Whether or not this Store is 'initialized.' Just gets set to |true| once
- // Initialize() is called.
- bool initialized_;
-
- base::WeakPtrFactory<NoopStore> weak_ptr_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(NoopStore);
-};
-
-} // namespace download
-
-#endif // COMPONENTS_DOWNLOAD_INTERNAL_NOOP_STORE_H_
diff --git a/chromium/components/download/internal/proto/BUILD.gn b/chromium/components/download/internal/proto/BUILD.gn
new file mode 100644
index 00000000000..9d6c33e751f
--- /dev/null
+++ b/chromium/components/download/internal/proto/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+ visibility = [
+ "//components/download/content/factory",
+ "//components/download/internal:*",
+ ]
+
+ sources = [
+ "entry.proto",
+ "request.proto",
+ "scheduling.proto",
+ ]
+}
diff --git a/chromium/components/download/internal/proto/entry.proto b/chromium/components/download/internal/proto/entry.proto
new file mode 100644
index 00000000000..426588a29e6
--- /dev/null
+++ b/chromium/components/download/internal/proto/entry.proto
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+import "request.proto";
+import "scheduling.proto";
+
+// This should stay in sync with the DownloadClient enum
+// (components/download/public/clients.h).
+enum DownloadClient {
+ INVALID = 0;
+ TEST = 1;
+ TEST_2 = 2;
+ TEST_3 = 3;
+ OFFLINE_PAGE_PREFETCH = 4;
+ BOUNDARY = 5;
+}
+
+// Stores the request params, internal state, metrics and metadata associated
+// with a download request.
+message Entry {
+ // This should stay in sync with the State enum
+ // (components/download/internal/entry.h).
+ enum State {
+ NEW = 0;
+ AVAILABLE = 1;
+ ACTIVE = 2;
+ PAUSED = 3;
+ COMPLETE = 4;
+ }
+
+ // Identification Parameters.
+ optional DownloadClient name_space = 1;
+ optional string guid = 2;
+
+ // Requested Parameters.
+ optional SchedulingParams scheduling_params = 3;
+ optional RequestParams request_params = 4;
+
+ // Internal Tracking States.
+ optional State state = 5;
+ optional string target_file_path = 6;
+
+ // Uses internal time representation.
+ optional int64 create_time = 7;
+ optional int64 completion_time = 8;
+
+ optional uint32 attempt_count = 9;
+}
diff --git a/chromium/components/download/internal/proto/request.proto b/chromium/components/download/internal/proto/request.proto
new file mode 100644
index 00000000000..173c6856182
--- /dev/null
+++ b/chromium/components/download/internal/proto/request.proto
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+message RequestHeader {
+ optional string key = 1;
+ optional string value = 2;
+}
+
+// Stores the HTTP request params associated with a download request.
+message RequestParams {
+ optional string url = 1;
+ optional string method = 2;
+ repeated RequestHeader headers = 3;
+}
diff --git a/chromium/components/download/internal/proto/scheduling.proto b/chromium/components/download/internal/proto/scheduling.proto
new file mode 100644
index 00000000000..2b8cc7ac747
--- /dev/null
+++ b/chromium/components/download/internal/proto/scheduling.proto
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package protodb;
+
+// Stores the scheduling params associated with a download request.
+message SchedulingParams {
+ // This should stay in sync with the NetworkRequirements enum
+ // (components/download/public/download_params.h).
+ enum NetworkRequirements {
+ NONE = 0;
+ OPTIMISTIC = 1;
+ UNMETERED = 2;
+ }
+
+ // This should stay in sync with the BatteryRequirements enum
+ // (components/download/public/download_params.h).
+ enum BatteryRequirements {
+ BATTERY_INSENSITIVE = 0;
+ BATTERY_SENSITIVE = 1;
+ }
+
+ // This should stay in sync with the Priority enum
+ // (components/download/public/download_params.h).
+ enum Priority {
+ LOW = 0;
+ NORMAL = 1;
+ HIGH = 2;
+ UI = 3;
+ }
+
+ // Uses internal time representation.
+ optional int64 cancel_time = 2;
+
+ optional Priority priority = 3;
+ optional NetworkRequirements network_requirements = 4;
+ optional BatteryRequirements battery_requirements = 5;
+}
diff --git a/chromium/components/download/internal/proto_conversions.cc b/chromium/components/download/internal/proto_conversions.cc
new file mode 100644
index 00000000000..f78f521b054
--- /dev/null
+++ b/chromium/components/download/internal/proto_conversions.cc
@@ -0,0 +1,307 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "components/download/internal/proto_conversions.h"
+#include "net/http/http_request_headers.h"
+
+namespace download {
+
+protodb::Entry_State ProtoConversions::RequestStateToProto(Entry::State state) {
+ switch (state) {
+ case Entry::State::NEW:
+ return protodb::Entry_State_NEW;
+ case Entry::State::AVAILABLE:
+ return protodb::Entry_State_AVAILABLE;
+ case Entry::State::ACTIVE:
+ return protodb::Entry_State_ACTIVE;
+ case Entry::State::PAUSED:
+ return protodb::Entry_State_PAUSED;
+ case Entry::State::COMPLETE:
+ return protodb::Entry_State_COMPLETE;
+ case Entry::State::COUNT:
+ break;
+ }
+
+ NOTREACHED();
+ return protodb::Entry_State_NEW;
+}
+
+Entry::State ProtoConversions::RequestStateFromProto(
+ protodb::Entry_State state) {
+ switch (state) {
+ case protodb::Entry_State_NEW:
+ return Entry::State::NEW;
+ case protodb::Entry_State_AVAILABLE:
+ return Entry::State::AVAILABLE;
+ case protodb::Entry_State_ACTIVE:
+ return Entry::State::ACTIVE;
+ case protodb::Entry_State_PAUSED:
+ return Entry::State::PAUSED;
+ case protodb::Entry_State_COMPLETE:
+ return Entry::State::COMPLETE;
+ }
+
+ NOTREACHED();
+ return Entry::State::NEW;
+}
+
+protodb::DownloadClient ProtoConversions::DownloadClientToProto(
+ DownloadClient client) {
+ switch (client) {
+ case DownloadClient::INVALID:
+ return protodb::DownloadClient::INVALID;
+ case DownloadClient::TEST:
+ return protodb::DownloadClient::TEST;
+ case DownloadClient::TEST_2:
+ return protodb::DownloadClient::TEST_2;
+ case DownloadClient::TEST_3:
+ return protodb::DownloadClient::TEST_3;
+ case DownloadClient::OFFLINE_PAGE_PREFETCH:
+ return protodb::DownloadClient::OFFLINE_PAGE_PREFETCH;
+ case DownloadClient::BOUNDARY:
+ return protodb::DownloadClient::BOUNDARY;
+ }
+
+ NOTREACHED();
+ return protodb::DownloadClient::INVALID;
+}
+
+DownloadClient ProtoConversions::DownloadClientFromProto(
+ protodb::DownloadClient client) {
+ switch (client) {
+ case protodb::DownloadClient::INVALID:
+ return DownloadClient::INVALID;
+ case protodb::DownloadClient::TEST:
+ return DownloadClient::TEST;
+ case protodb::DownloadClient::TEST_2:
+ return DownloadClient::TEST_2;
+ case protodb::DownloadClient::TEST_3:
+ return DownloadClient::TEST_3;
+ case protodb::DownloadClient::OFFLINE_PAGE_PREFETCH:
+ return DownloadClient::OFFLINE_PAGE_PREFETCH;
+ case protodb::DownloadClient::BOUNDARY:
+ return DownloadClient::BOUNDARY;
+ }
+
+ NOTREACHED();
+ return DownloadClient::INVALID;
+}
+
+SchedulingParams::NetworkRequirements
+ProtoConversions::NetworkRequirementsFromProto(
+ protodb::SchedulingParams_NetworkRequirements network_requirements) {
+ switch (network_requirements) {
+ case protodb::SchedulingParams_NetworkRequirements_NONE:
+ return SchedulingParams::NetworkRequirements::NONE;
+ case protodb::SchedulingParams_NetworkRequirements_OPTIMISTIC:
+ return SchedulingParams::NetworkRequirements::OPTIMISTIC;
+ case protodb::SchedulingParams_NetworkRequirements_UNMETERED:
+ return SchedulingParams::NetworkRequirements::UNMETERED;
+ }
+
+ NOTREACHED();
+ return SchedulingParams::NetworkRequirements::NONE;
+}
+
+protodb::SchedulingParams_NetworkRequirements
+ProtoConversions::NetworkRequirementsToProto(
+ SchedulingParams::NetworkRequirements network_requirements) {
+ switch (network_requirements) {
+ case SchedulingParams::NetworkRequirements::NONE:
+ return protodb::SchedulingParams_NetworkRequirements_NONE;
+ case SchedulingParams::NetworkRequirements::OPTIMISTIC:
+ return protodb::SchedulingParams_NetworkRequirements_OPTIMISTIC;
+ case SchedulingParams::NetworkRequirements::UNMETERED:
+ return protodb::SchedulingParams_NetworkRequirements_UNMETERED;
+ case SchedulingParams::NetworkRequirements::COUNT:
+ break;
+ }
+
+ NOTREACHED();
+ return protodb::SchedulingParams_NetworkRequirements_NONE;
+}
+
+SchedulingParams::BatteryRequirements
+ProtoConversions::BatteryRequirementsFromProto(
+ protodb::SchedulingParams_BatteryRequirements battery_requirements) {
+ switch (battery_requirements) {
+ case protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE:
+ return SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+ case protodb::SchedulingParams_BatteryRequirements_BATTERY_SENSITIVE:
+ return SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ }
+
+ NOTREACHED();
+ return SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+}
+
+protodb::SchedulingParams_BatteryRequirements
+ProtoConversions::BatteryRequirementsToProto(
+ SchedulingParams::BatteryRequirements battery_requirements) {
+ switch (battery_requirements) {
+ case SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE:
+ return protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE;
+ case SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE:
+ return protodb::SchedulingParams_BatteryRequirements_BATTERY_SENSITIVE;
+ case SchedulingParams::BatteryRequirements::COUNT:
+ break;
+ }
+
+ NOTREACHED();
+ return protodb::SchedulingParams_BatteryRequirements_BATTERY_INSENSITIVE;
+}
+
+SchedulingParams::Priority ProtoConversions::SchedulingPriorityFromProto(
+ protodb::SchedulingParams_Priority priority) {
+ switch (priority) {
+ case protodb::SchedulingParams_Priority_LOW:
+ return SchedulingParams::Priority::LOW;
+ case protodb::SchedulingParams_Priority_NORMAL:
+ return SchedulingParams::Priority::NORMAL;
+ case protodb::SchedulingParams_Priority_HIGH:
+ return SchedulingParams::Priority::HIGH;
+ case protodb::SchedulingParams_Priority_UI:
+ return SchedulingParams::Priority::UI;
+ }
+
+ NOTREACHED();
+ return SchedulingParams::Priority::LOW;
+}
+
+protodb::SchedulingParams_Priority ProtoConversions::SchedulingPriorityToProto(
+ SchedulingParams::Priority priority) {
+ switch (priority) {
+ case SchedulingParams::Priority::LOW:
+ return protodb::SchedulingParams_Priority_LOW;
+ case SchedulingParams::Priority::NORMAL:
+ return protodb::SchedulingParams_Priority_NORMAL;
+ case SchedulingParams::Priority::HIGH:
+ return protodb::SchedulingParams_Priority_HIGH;
+ case SchedulingParams::Priority::UI:
+ return protodb::SchedulingParams_Priority_UI;
+ case SchedulingParams::Priority::COUNT:
+ break;
+ }
+
+ NOTREACHED();
+ return protodb::SchedulingParams_Priority_LOW;
+}
+
+SchedulingParams ProtoConversions::SchedulingParamsFromProto(
+ const protodb::SchedulingParams& proto) {
+ SchedulingParams scheduling_params;
+
+ scheduling_params.cancel_time =
+ base::Time::FromInternalValue(proto.cancel_time());
+ scheduling_params.priority = SchedulingPriorityFromProto(proto.priority());
+ scheduling_params.network_requirements =
+ NetworkRequirementsFromProto(proto.network_requirements());
+ scheduling_params.battery_requirements =
+ BatteryRequirementsFromProto(proto.battery_requirements());
+
+ return scheduling_params;
+}
+
+void ProtoConversions::SchedulingParamsToProto(
+ const SchedulingParams& scheduling_params,
+ protodb::SchedulingParams* proto) {
+ proto->set_cancel_time(scheduling_params.cancel_time.ToInternalValue());
+ proto->set_priority(SchedulingPriorityToProto(scheduling_params.priority));
+ proto->set_network_requirements(
+ NetworkRequirementsToProto(scheduling_params.network_requirements));
+ proto->set_battery_requirements(
+ BatteryRequirementsToProto(scheduling_params.battery_requirements));
+}
+
+RequestParams ProtoConversions::RequestParamsFromProto(
+ const protodb::RequestParams& proto) {
+ RequestParams request_params;
+ request_params.url = GURL(proto.url());
+ request_params.method = proto.method();
+
+ for (int i = 0; i < proto.headers_size(); i++) {
+ protodb::RequestHeader header = proto.headers(i);
+ request_params.request_headers.SetHeader(header.key(), header.value());
+ }
+
+ return request_params;
+}
+
+void ProtoConversions::RequestParamsToProto(const RequestParams& request_params,
+ protodb::RequestParams* proto) {
+ proto->set_url(request_params.url.spec());
+ proto->set_method(request_params.method);
+
+ int i = 0;
+ net::HttpRequestHeaders::Iterator iter(request_params.request_headers);
+ while (iter.GetNext()) {
+ protodb::RequestHeader* header = proto->add_headers();
+ header->set_key(iter.name());
+ header->set_value(iter.value());
+ i++;
+ }
+}
+
+Entry ProtoConversions::EntryFromProto(const protodb::Entry& proto) {
+ Entry entry;
+
+ entry.guid = proto.guid();
+ entry.client = DownloadClientFromProto(proto.name_space());
+ entry.scheduling_params =
+ SchedulingParamsFromProto(proto.scheduling_params());
+ entry.request_params = RequestParamsFromProto(proto.request_params());
+ entry.state = RequestStateFromProto(proto.state());
+ entry.target_file_path =
+ base::FilePath::FromUTF8Unsafe(proto.target_file_path());
+ entry.create_time = base::Time::FromInternalValue(proto.create_time());
+ entry.completion_time =
+ base::Time::FromInternalValue(proto.completion_time());
+ entry.attempt_count = proto.attempt_count();
+
+ return entry;
+}
+
+protodb::Entry ProtoConversions::EntryToProto(const Entry& entry) {
+ protodb::Entry proto;
+
+ proto.set_guid(entry.guid);
+ proto.set_name_space(DownloadClientToProto(entry.client));
+ SchedulingParamsToProto(entry.scheduling_params,
+ proto.mutable_scheduling_params());
+ RequestParamsToProto(entry.request_params, proto.mutable_request_params());
+ proto.set_state(RequestStateToProto(entry.state));
+ proto.set_target_file_path(entry.target_file_path.AsUTF8Unsafe());
+ proto.set_create_time(entry.create_time.ToInternalValue());
+ proto.set_completion_time(entry.completion_time.ToInternalValue());
+ proto.set_attempt_count(entry.attempt_count);
+
+ return proto;
+}
+
+std::unique_ptr<std::vector<Entry>> ProtoConversions::EntryVectorFromProto(
+ std::unique_ptr<std::vector<protodb::Entry>> protos) {
+ auto entries = base::MakeUnique<std::vector<Entry>>();
+ for (auto& proto : *protos) {
+ entries->push_back(EntryFromProto(proto));
+ }
+
+ return entries;
+}
+
+std::unique_ptr<std::vector<protodb::Entry>>
+ProtoConversions::EntryVectorToProto(
+ std::unique_ptr<std::vector<Entry>> entries) {
+ auto protos = base::MakeUnique<std::vector<protodb::Entry>>();
+ for (auto& entry : *entries) {
+ protos->push_back(EntryToProto(entry));
+ }
+
+ return protos;
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/proto_conversions.h b/chromium/components/download/internal/proto_conversions.h
new file mode 100644
index 00000000000..4d2e4e53661
--- /dev/null
+++ b/chromium/components/download/internal/proto_conversions.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
+
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto/entry.pb.h"
+#include "components/download/internal/proto/request.pb.h"
+#include "components/download/internal/proto/scheduling.pb.h"
+
+namespace download {
+
+class ProtoConversions {
+ public:
+ static Entry EntryFromProto(const protodb::Entry& proto);
+
+ static protodb::Entry EntryToProto(const Entry& entry);
+
+ static std::unique_ptr<std::vector<Entry>> EntryVectorFromProto(
+ std::unique_ptr<std::vector<protodb::Entry>> proto);
+
+ static std::unique_ptr<std::vector<protodb::Entry>> EntryVectorToProto(
+ std::unique_ptr<std::vector<Entry>> entries);
+
+ protected:
+ static protodb::Entry_State RequestStateToProto(Entry::State state);
+ static Entry::State RequestStateFromProto(protodb::Entry_State state);
+
+ static protodb::DownloadClient DownloadClientToProto(DownloadClient client);
+ static DownloadClient DownloadClientFromProto(protodb::DownloadClient client);
+
+ static SchedulingParams::NetworkRequirements NetworkRequirementsFromProto(
+ protodb::SchedulingParams_NetworkRequirements network_requirements);
+ static protodb::SchedulingParams_NetworkRequirements
+ NetworkRequirementsToProto(
+ SchedulingParams::NetworkRequirements network_requirements);
+
+ static SchedulingParams::BatteryRequirements BatteryRequirementsFromProto(
+ protodb::SchedulingParams_BatteryRequirements battery_requirements);
+ static protodb::SchedulingParams_BatteryRequirements
+ BatteryRequirementsToProto(
+ SchedulingParams::BatteryRequirements battery_requirements);
+
+ static SchedulingParams::Priority SchedulingPriorityFromProto(
+ protodb::SchedulingParams_Priority priority);
+ static protodb::SchedulingParams_Priority SchedulingPriorityToProto(
+ SchedulingParams::Priority priority);
+
+ static SchedulingParams SchedulingParamsFromProto(
+ const protodb::SchedulingParams& proto);
+ static void SchedulingParamsToProto(const SchedulingParams& scheduling_params,
+ protodb::SchedulingParams* proto);
+
+ static RequestParams RequestParamsFromProto(
+ const protodb::RequestParams& proto);
+ static void RequestParamsToProto(const RequestParams& request_params,
+ protodb::RequestParams* proto);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_PROTO_CONVERSIONS_H_
diff --git a/chromium/components/download/internal/proto_conversions_unittest.cc b/chromium/components/download/internal/proto_conversions_unittest.cc
new file mode 100644
index 00000000000..ea1e9cbf4fd
--- /dev/null
+++ b/chromium/components/download/internal/proto_conversions_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/guid.h"
+#include "base/memory/ptr_util.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/proto_conversions.h"
+#include "components/download/internal/test/entry_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+std::string TEST_URL = "https://google.com";
+
+} // namespace
+
+namespace download {
+
+class ProtoConversionsTest : public testing::Test, public ProtoConversions {
+ public:
+ ~ProtoConversionsTest() override {}
+};
+
+TEST_F(ProtoConversionsTest, StateConversion) {
+ Entry::State states[] = {Entry::State::NEW, Entry::State::AVAILABLE,
+ Entry::State::ACTIVE, Entry::State::PAUSED,
+ Entry::State::COMPLETE};
+ for (auto state : states) {
+ ASSERT_EQ(state, RequestStateFromProto(RequestStateToProto(state)));
+ }
+}
+
+TEST_F(ProtoConversionsTest, DownloadClientConversion) {
+ DownloadClient clients[] = {DownloadClient::INVALID, DownloadClient::TEST,
+ DownloadClient::TEST_2, DownloadClient::BOUNDARY};
+ for (auto client : clients) {
+ ASSERT_EQ(client, DownloadClientFromProto(DownloadClientToProto(client)));
+ }
+}
+
+TEST_F(ProtoConversionsTest, NetworkRequirementsConversion) {
+ SchedulingParams::NetworkRequirements values[] = {
+ SchedulingParams::NetworkRequirements::NONE,
+ SchedulingParams::NetworkRequirements::OPTIMISTIC,
+ SchedulingParams::NetworkRequirements::UNMETERED};
+ for (auto value : values) {
+ ASSERT_EQ(value,
+ NetworkRequirementsFromProto(NetworkRequirementsToProto(value)));
+ }
+}
+
+TEST_F(ProtoConversionsTest, BatteryRequirementsConversion) {
+ SchedulingParams::BatteryRequirements values[] = {
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE,
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE};
+ for (auto value : values) {
+ ASSERT_EQ(value,
+ BatteryRequirementsFromProto(BatteryRequirementsToProto(value)));
+ }
+}
+
+TEST_F(ProtoConversionsTest, SchedulingPriorityConversion) {
+ SchedulingParams::Priority values[] = {
+ SchedulingParams::Priority::LOW, SchedulingParams::Priority::NORMAL,
+ SchedulingParams::Priority::HIGH, SchedulingParams::Priority::UI,
+ SchedulingParams::Priority::DEFAULT};
+ for (auto value : values) {
+ ASSERT_EQ(value,
+ SchedulingPriorityFromProto(SchedulingPriorityToProto(value)));
+ }
+}
+
+TEST_F(ProtoConversionsTest, SchedulingParamsConversion) {
+ SchedulingParams expected;
+ expected.cancel_time = base::Time::Now();
+ expected.priority = SchedulingParams::Priority::DEFAULT;
+ expected.network_requirements =
+ SchedulingParams::NetworkRequirements::OPTIMISTIC;
+ expected.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+
+ protodb::SchedulingParams proto;
+ SchedulingParamsToProto(expected, &proto);
+ SchedulingParams actual = SchedulingParamsFromProto(proto);
+ EXPECT_EQ(expected.cancel_time, actual.cancel_time);
+ EXPECT_EQ(SchedulingParams::Priority::DEFAULT, actual.priority);
+ EXPECT_EQ(SchedulingParams::NetworkRequirements::OPTIMISTIC,
+ actual.network_requirements);
+ EXPECT_EQ(SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+ actual.battery_requirements);
+}
+
+TEST_F(ProtoConversionsTest, RequestParamsWithHeadersConversion) {
+ RequestParams expected;
+ expected.url = GURL(TEST_URL);
+ expected.method = "GET";
+ expected.request_headers.SetHeader("key1", "value1");
+ expected.request_headers.SetHeader("key2", "value2");
+
+ protodb::RequestParams proto;
+ RequestParamsToProto(expected, &proto);
+ RequestParams actual = RequestParamsFromProto(proto);
+
+ EXPECT_EQ(expected.url, actual.url);
+ EXPECT_EQ(expected.method, actual.method);
+
+ std::string out;
+ actual.request_headers.GetHeader("key1", &out);
+ EXPECT_EQ("value1", out);
+ actual.request_headers.GetHeader("key2", &out);
+ EXPECT_EQ("value2", out);
+ EXPECT_EQ(expected.request_headers.ToString(),
+ actual.request_headers.ToString());
+}
+
+TEST_F(ProtoConversionsTest, EntryConversion) {
+ Entry expected = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
+ Entry actual = EntryFromProto(EntryToProto(expected));
+ EXPECT_TRUE(test::CompareEntry(&expected, &actual));
+
+ expected = test::BuildEntry(
+ DownloadClient::TEST, base::GenerateGUID(), base::Time::Now(),
+ SchedulingParams::NetworkRequirements::OPTIMISTIC,
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+ SchedulingParams::Priority::HIGH, GURL(TEST_URL), "GET",
+ Entry::State::ACTIVE, base::FilePath(FILE_PATH_LITERAL("/test/xyz")),
+ base::Time::Now(), base::Time::Now(), 3);
+ actual = EntryFromProto(EntryToProto(expected));
+ EXPECT_TRUE(test::CompareEntry(&expected, &actual));
+}
+
+TEST_F(ProtoConversionsTest, EntryVectorConversion) {
+ std::vector<Entry> expected;
+ expected.push_back(
+ test::BuildEntry(DownloadClient::TEST, base::GenerateGUID()));
+ expected.push_back(
+ test::BuildEntry(DownloadClient::TEST_2, base::GenerateGUID()));
+ expected.push_back(test::BuildEntry(
+ DownloadClient::TEST, base::GenerateGUID(), base::Time::Now(),
+ SchedulingParams::NetworkRequirements::OPTIMISTIC,
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE,
+ SchedulingParams::Priority::HIGH, GURL(TEST_URL), "GET",
+ Entry::State::ACTIVE, base::FilePath(FILE_PATH_LITERAL("/test/xyz")),
+ base::Time::Now(), base::Time::Now(), 2));
+
+ auto actual = EntryVectorFromProto(
+ EntryVectorToProto(base::MakeUnique<std::vector<Entry>>(expected)));
+ EXPECT_TRUE(test::CompareEntryList(expected, *actual));
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/scheduler/battery_listener.cc b/chromium/components/download/internal/scheduler/battery_listener.cc
deleted file mode 100644
index b015ca15ffd..00000000000
--- a/chromium/components/download/internal/scheduler/battery_listener.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/internal/scheduler/battery_listener.h"
-
-#include "base/power_monitor/power_monitor.h"
-
-namespace download {
-
-// Helper function that converts the battery status to battery requirement.
-SchedulingParams::BatteryRequirements ToBatteryRequirement(
- bool on_battery_power) {
- return on_battery_power
- ? SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE
- : SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
-}
-
-BatteryListener::BatteryListener() = default;
-
-BatteryListener::~BatteryListener() {
- Stop();
-}
-
-SchedulingParams::BatteryRequirements BatteryListener::CurrentBatteryStatus()
- const {
- return ToBatteryRequirement(base::PowerMonitor::Get()->IsOnBatteryPower());
-}
-
-void BatteryListener::Start() {
- base::PowerMonitor* monitor = base::PowerMonitor::Get();
- DCHECK(monitor);
- monitor->AddObserver(this);
-}
-
-void BatteryListener::Stop() {
- base::PowerMonitor::Get()->RemoveObserver(this);
-}
-
-void BatteryListener::AddObserver(Observer* observer) {
- observers_.AddObserver(observer);
-}
-
-void BatteryListener::RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
-}
-
-void BatteryListener::OnPowerStateChange(bool on_battery_power) {
- NotifyBatteryChange(ToBatteryRequirement(on_battery_power));
-}
-
-void BatteryListener::NotifyBatteryChange(
- SchedulingParams::BatteryRequirements current_battery) {
- for (auto& observer : observers_)
- observer.OnBatteryChange(current_battery);
-}
-
-} // namespace download
diff --git a/chromium/components/download/internal/scheduler/battery_listener.h b/chromium/components/download/internal/scheduler/battery_listener.h
deleted file mode 100644
index 2ddc6109b55..00000000000
--- a/chromium/components/download/internal/scheduler/battery_listener.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_BATTERY_LISTENER_H_
-#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_BATTERY_LISTENER_H_
-
-#include <memory>
-
-#include "base/observer_list.h"
-#include "base/power_monitor/power_observer.h"
-#include "components/download/public/download_params.h"
-
-namespace download {
-
-// Listen to battery charing state changes and notify observers in download
-// service.
-// TODO(xingliu): Converts to device service when it's fully done.
-class BatteryListener : public base::PowerObserver {
- public:
- class Observer {
- public:
- // Called when certain battery requirements are meet.
- virtual void OnBatteryChange(
- SchedulingParams::BatteryRequirements battery_status) = 0;
- };
-
- BatteryListener();
- ~BatteryListener() override;
-
- // Retrieves the current minimum battery requirement.
- SchedulingParams::BatteryRequirements CurrentBatteryStatus() const;
-
- // Start to listen to battery change.
- void Start();
-
- // Stop to listen to battery change.
- void Stop();
-
- // Adds/Removes observers.
- void AddObserver(Observer* observer);
- void RemoveObserver(Observer* observer);
-
- private:
- // base::PowerObserver implementation.
- void OnPowerStateChange(bool on_battery_power) override;
-
- // Notifies |observers_| about battery requirement change.
- void NotifyBatteryChange(SchedulingParams::BatteryRequirements);
-
- // Observers to monitor battery change in download service.
- base::ObserverList<Observer> observers_;
-
- DISALLOW_COPY_AND_ASSIGN(BatteryListener);
-};
-
-} // namespace download
-
-#endif // COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_BATTERY_LISTENER_H_
diff --git a/chromium/components/download/internal/scheduler/battery_listener_unittest.cc b/chromium/components/download/internal/scheduler/battery_listener_unittest.cc
deleted file mode 100644
index 3ddacffbf10..00000000000
--- a/chromium/components/download/internal/scheduler/battery_listener_unittest.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/internal/scheduler/battery_listener.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/test/power_monitor_test_base.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace download {
-namespace {
-
-using BatteryRequirements = SchedulingParams::BatteryRequirements;
-
-class MockObserver : public BatteryListener::Observer {
- public:
- MOCK_METHOD1(OnBatteryChange, void(SchedulingParams::BatteryRequirements));
-};
-
-class BatteryListenerTest : public testing::Test {
- public:
- BatteryListenerTest() {}
- ~BatteryListenerTest() override = default;
-
- void CallBatteryChange(bool on_battery_power) {
- DCHECK(listener_);
- static_cast<base::PowerObserver*>(listener_.get())
- ->OnPowerStateChange(on_battery_power);
- }
-
- void SetUp() override {
- power_monitor_ = base::MakeUnique<base::PowerMonitor>(
- base::MakeUnique<base::PowerMonitorTestSource>());
-
- observer_ = base::MakeUnique<MockObserver>();
- listener_ = base::MakeUnique<BatteryListener>();
- listener_->AddObserver(observer_.get());
- }
-
- void TearDown() override {
- listener_.reset();
- observer_.reset();
- }
-
- std::unique_ptr<BatteryListener> listener_;
- std::unique_ptr<MockObserver> observer_;
-
- base::MessageLoop message_loop_;
- std::unique_ptr<base::PowerMonitor> power_monitor_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(BatteryListenerTest);
-};
-
-// Ensures observer methods are corrected called.
-TEST_F(BatteryListenerTest, NotifyObservers) {
- listener_->Start();
- EXPECT_CALL(*observer_.get(),
- OnBatteryChange(BatteryRequirements::BATTERY_INSENSITIVE))
- .RetiresOnSaturation();
- CallBatteryChange(true);
-
- EXPECT_CALL(*observer_.get(),
- OnBatteryChange(BatteryRequirements::BATTERY_SENSITIVE))
- .RetiresOnSaturation();
- CallBatteryChange(false);
- listener_->Stop();
-};
-
-} // namespace
-} // namespace download
diff --git a/chromium/components/download/internal/scheduler/device_status.cc b/chromium/components/download/internal/scheduler/device_status.cc
new file mode 100644
index 00000000000..80c88c0053d
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/device_status.cc
@@ -0,0 +1,72 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/scheduler/device_status.h"
+
+namespace download {
+
+DeviceStatus::Result::Result()
+ : meets_battery_requirement(false), meets_network_requirement(false) {}
+
+bool DeviceStatus::Result::MeetsRequirements() const {
+ return meets_battery_requirement && meets_network_requirement;
+}
+
+DeviceStatus::DeviceStatus()
+ : battery_status(BatteryStatus::NOT_CHARGING),
+ network_status(NetworkStatus::DISCONNECTED) {}
+
+DeviceStatus::DeviceStatus(BatteryStatus battery, NetworkStatus network)
+ : battery_status(battery), network_status(network) {}
+
+bool DeviceStatus::operator==(const DeviceStatus& rhs) const {
+ return network_status == rhs.network_status &&
+ battery_status == rhs.battery_status;
+}
+
+DeviceStatus::Result DeviceStatus::MeetsCondition(
+ const SchedulingParams& params) const {
+ DeviceStatus::Result result;
+
+ switch (params.battery_requirements) {
+ case SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE:
+ result.meets_battery_requirement = true;
+ break;
+ case SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE:
+ result.meets_battery_requirement =
+ battery_status == BatteryStatus::CHARGING;
+ break;
+ default:
+ NOTREACHED();
+ }
+ switch (params.network_requirements) {
+ case SchedulingParams::NetworkRequirements::NONE:
+ result.meets_network_requirement =
+ network_status != NetworkStatus::DISCONNECTED;
+ break;
+ case SchedulingParams::NetworkRequirements::OPTIMISTIC:
+ case SchedulingParams::NetworkRequirements::UNMETERED:
+ result.meets_network_requirement =
+ network_status == NetworkStatus::UNMETERED;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return result;
+}
+
+Criteria::Criteria()
+ : requires_battery_charging(true), requires_unmetered_network(true) {}
+
+Criteria::Criteria(bool requires_battery_charging,
+ bool requires_unmetered_network)
+ : requires_battery_charging(requires_battery_charging),
+ requires_unmetered_network(requires_unmetered_network) {}
+
+bool Criteria::operator==(const Criteria& other) const {
+ return requires_battery_charging == other.requires_battery_charging &&
+ requires_unmetered_network == other.requires_unmetered_network;
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/scheduler/device_status.h b/chromium/components/download/internal/scheduler/device_status.h
new file mode 100644
index 00000000000..720a1266f0f
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/device_status.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_H_
+
+#include "components/download/public/download_params.h"
+
+namespace download {
+
+// Battery status used in download service.
+enum class BatteryStatus {
+ CHARGING = 0,
+ NOT_CHARGING = 1,
+};
+
+// NetworkStatus should mostly one to one map to
+// SchedulingParams::NetworkRequirements. Has coarser granularity than
+// network connection type.
+enum class NetworkStatus {
+ DISCONNECTED = 0,
+ UNMETERED = 1, // WIFI or Ethernet.
+ METERED = 2, // Mobile networks.
+};
+
+// Contains battery and network status.
+struct DeviceStatus {
+ DeviceStatus();
+ DeviceStatus(BatteryStatus battery, NetworkStatus network);
+
+ struct Result {
+ Result();
+ bool MeetsRequirements() const;
+ bool meets_battery_requirement;
+ bool meets_network_requirement;
+ };
+
+ BatteryStatus battery_status;
+ NetworkStatus network_status;
+
+ bool operator==(const DeviceStatus& rhs) const;
+
+ // Returns if the current device status meets all the conditions defined in
+ // the scheduling parameters.
+ Result MeetsCondition(const SchedulingParams& params) const;
+};
+
+// The criteria when the background download task should start.
+struct Criteria {
+ Criteria();
+ Criteria(bool requires_battery_charging, bool requires_unmetered_network);
+ bool operator==(const Criteria& other) const;
+ bool requires_battery_charging;
+ bool requires_unmetered_network;
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_H_
diff --git a/chromium/components/download/internal/scheduler/device_status_listener.cc b/chromium/components/download/internal/scheduler/device_status_listener.cc
new file mode 100644
index 00000000000..ba277b04489
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/device_status_listener.cc
@@ -0,0 +1,122 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/scheduler/device_status_listener.h"
+
+#include "base/power_monitor/power_monitor.h"
+
+namespace download {
+
+namespace {
+
+// Converts |on_battery_power| to battery status.
+BatteryStatus ToBatteryStatus(bool on_battery_power) {
+ return on_battery_power ? BatteryStatus::NOT_CHARGING
+ : BatteryStatus::CHARGING;
+}
+
+// Converts a ConnectionType to NetworkStatus.
+NetworkStatus ToNetworkStatus(net::NetworkChangeNotifier::ConnectionType type) {
+ switch (type) {
+ case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+ case net::NetworkChangeNotifier::CONNECTION_WIFI:
+ return NetworkStatus::UNMETERED;
+ case net::NetworkChangeNotifier::CONNECTION_2G:
+ case net::NetworkChangeNotifier::CONNECTION_3G:
+ case net::NetworkChangeNotifier::CONNECTION_4G:
+ return NetworkStatus::METERED;
+ case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ case net::NetworkChangeNotifier::CONNECTION_NONE:
+ case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
+ return NetworkStatus::DISCONNECTED;
+ }
+ NOTREACHED();
+ return NetworkStatus::DISCONNECTED;
+}
+
+} // namespace
+
+DeviceStatusListener::DeviceStatusListener(const base::TimeDelta& delay)
+ : observer_(nullptr), listening_(false), delay_(delay) {}
+
+DeviceStatusListener::~DeviceStatusListener() {
+ Stop();
+}
+
+const DeviceStatus& DeviceStatusListener::CurrentDeviceStatus() const {
+ DCHECK(listening_) << "Call Start() before querying the status.";
+ return status_;
+}
+
+void DeviceStatusListener::Start(DeviceStatusListener::Observer* observer) {
+ if (listening_)
+ return;
+
+ DCHECK(observer);
+ observer_ = observer;
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ DCHECK(power_monitor);
+ power_monitor->AddObserver(this);
+
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+
+ status_.battery_status =
+ ToBatteryStatus(base::PowerMonitor::Get()->IsOnBatteryPower());
+ status_.network_status =
+ ToNetworkStatus(net::NetworkChangeNotifier::GetConnectionType());
+ listening_ = true;
+}
+
+void DeviceStatusListener::Stop() {
+ if (!listening_)
+ return;
+
+ base::PowerMonitor::Get()->RemoveObserver(this);
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+
+ status_ = DeviceStatus();
+ listening_ = false;
+ observer_ = nullptr;
+}
+
+void DeviceStatusListener::OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ NetworkStatus new_network_status = ToNetworkStatus(type);
+ if (status_.network_status == new_network_status)
+ return;
+
+ bool change_to_online =
+ (status_.network_status == NetworkStatus::DISCONNECTED) &&
+ (new_network_status != NetworkStatus::DISCONNECTED);
+
+ // It's unreliable to send requests immediately after the network becomes
+ // online. Notify network change to the observer after a delay.
+ if (change_to_online) {
+ timer_.Start(
+ FROM_HERE, delay_,
+ base::Bind(&DeviceStatusListener::NotifyNetworkChangeAfterDelay,
+ base::Unretained(this), new_network_status));
+ } else {
+ status_.network_status = new_network_status;
+ timer_.Stop();
+ NotifyStatusChange();
+ }
+}
+
+void DeviceStatusListener::OnPowerStateChange(bool on_battery_power) {
+ status_.battery_status = ToBatteryStatus(on_battery_power);
+ NotifyStatusChange();
+}
+
+void DeviceStatusListener::NotifyStatusChange() {
+ observer_->OnDeviceStatusChanged(status_);
+}
+
+void DeviceStatusListener::NotifyNetworkChangeAfterDelay(
+ NetworkStatus network_status) {
+ status_.network_status = network_status;
+ NotifyStatusChange();
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/scheduler/device_status_listener.h b/chromium/components/download/internal/scheduler/device_status_listener.h
new file mode 100644
index 00000000000..a14a28c6f84
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/device_status_listener.h
@@ -0,0 +1,72 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_LISTENER_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_LISTENER_H_
+
+#include "base/power_monitor/power_observer.h"
+#include "base/timer/timer.h"
+#include "components/download/internal/scheduler/device_status.h"
+#include "net/base/network_change_notifier.h"
+
+namespace download {
+
+// Listens to network and battery status change and notifies the observer.
+class DeviceStatusListener
+ : public net::NetworkChangeNotifier::ConnectionTypeObserver,
+ public base::PowerObserver {
+ public:
+ class Observer {
+ public:
+ // Called when device status is changed.
+ virtual void OnDeviceStatusChanged(const DeviceStatus& device_status) = 0;
+ };
+
+ DeviceStatusListener(const base::TimeDelta& delay);
+ ~DeviceStatusListener() override;
+
+ // Returns the current device status for download scheduling.
+ const DeviceStatus& CurrentDeviceStatus() const;
+
+ // Starts/stops to listen network and battery change events, virtual for
+ // testing.
+ virtual void Start(DeviceStatusListener::Observer* observer);
+ virtual void Stop();
+
+ protected:
+ // The current device status.
+ DeviceStatus status_;
+
+ // The observer that listens to device status change events.
+ Observer* observer_;
+
+ // If we are actively listening to network and battery change events.
+ bool listening_;
+
+ private:
+ // net::NetworkChangeNotifier::ConnectionTypeObserver implementation.
+ void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ // base::PowerObserver implementation.
+ void OnPowerStateChange(bool on_battery_power) override;
+
+ // Notifies the observer about device status change.
+ void NotifyStatusChange();
+
+ // Called after a delay to notify the observer. See |delay_|.
+ void NotifyNetworkChangeAfterDelay(NetworkStatus network_status);
+
+ // Used to notify the observer after a delay when network becomes connected.
+ base::OneShotTimer timer_;
+
+ // The delay used by |timer_|.
+ base::TimeDelta delay_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceStatusListener);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_DEVICE_STATUS_LISTENER_H_
diff --git a/chromium/components/download/internal/scheduler/device_status_listener_unittest.cc b/chromium/components/download/internal/scheduler/device_status_listener_unittest.cc
new file mode 100644
index 00000000000..45fea61651c
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/device_status_listener_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/scheduler/device_status_listener.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/power_monitor_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::InSequence;
+using ConnectionTypeObserver =
+ net::NetworkChangeNotifier::ConnectionTypeObserver;
+using ConnectionType = net::NetworkChangeNotifier::ConnectionType;
+
+namespace download {
+namespace {
+
+// NetworkChangeNotifier that can change network type in tests.
+class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
+ public:
+ TestNetworkChangeNotifier()
+ : net::NetworkChangeNotifier(),
+ conn_type_(ConnectionType::CONNECTION_UNKNOWN) {}
+
+ // net::NetworkChangeNotifier implementation.
+ ConnectionType GetCurrentConnectionType() const override {
+ return conn_type_;
+ }
+
+ // Changes the network type.
+ void ChangeNetworkType(ConnectionType type) {
+ conn_type_ = type;
+ net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests(
+ type);
+ }
+
+ private:
+ ConnectionType conn_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
+};
+
+class MockObserver : public DeviceStatusListener::Observer {
+ public:
+ MOCK_METHOD1(OnDeviceStatusChanged, void(const DeviceStatus&));
+};
+
+class DeviceStatusListenerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ power_monitor_ = base::MakeUnique<base::PowerMonitor>(
+ base::MakeUnique<base::PowerMonitorTestSource>());
+
+ listener_ =
+ base::MakeUnique<DeviceStatusListener>(base::TimeDelta::FromSeconds(0));
+ }
+
+ void TearDown() override { listener_.reset(); }
+
+ // Simulates a network change call.
+ void ChangeNetworkType(ConnectionType type) {
+ test_network_notifier_.ChangeNetworkType(type);
+ }
+
+ // Simulates a battery change call.
+ void SimulateBatteryChange(bool on_battery_power) {
+ static_cast<base::PowerObserver*>(listener_.get())
+ ->OnPowerStateChange(on_battery_power);
+ }
+
+ protected:
+ std::unique_ptr<DeviceStatusListener> listener_;
+ MockObserver mock_observer_;
+
+ // Needed for network change notifier and power monitor.
+ base::MessageLoop message_loop_;
+ TestNetworkChangeNotifier test_network_notifier_;
+ std::unique_ptr<base::PowerMonitor> power_monitor_;
+};
+
+// Ensures the observer is notified when network condition changes.
+TEST_F(DeviceStatusListenerTest, NotifyObserverNetworkChange) {
+ listener_->Start(&mock_observer_);
+
+ // Initial states check.
+ DeviceStatus status = listener_->CurrentDeviceStatus();
+ EXPECT_EQ(NetworkStatus::DISCONNECTED, status.network_status);
+
+ // Network switch between mobile networks, the observer should be notified
+ // only once.
+ status.network_status = NetworkStatus::METERED;
+ EXPECT_CALL(mock_observer_, OnDeviceStatusChanged(status))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ ChangeNetworkType(ConnectionType::CONNECTION_4G);
+ ChangeNetworkType(ConnectionType::CONNECTION_3G);
+ ChangeNetworkType(ConnectionType::CONNECTION_2G);
+
+ // Verifies the online signal is sent in a post task after a delay.
+ EXPECT_EQ(NetworkStatus::DISCONNECTED,
+ listener_->CurrentDeviceStatus().network_status);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(NetworkStatus::METERED,
+ listener_->CurrentDeviceStatus().network_status);
+
+ // Network is switched between wifi and ethernet, the observer should be
+ // notified only once.
+ status.network_status = NetworkStatus::UNMETERED;
+ EXPECT_CALL(mock_observer_, OnDeviceStatusChanged(status))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ ChangeNetworkType(ConnectionType::CONNECTION_WIFI);
+ ChangeNetworkType(ConnectionType::CONNECTION_ETHERNET);
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(NetworkStatus::UNMETERED,
+ listener_->CurrentDeviceStatus().network_status);
+}
+
+// Ensures the observer is notified when battery condition changes.
+TEST_F(DeviceStatusListenerTest, NotifyObserverBatteryChange) {
+ InSequence s;
+ listener_->Start(&mock_observer_);
+ DeviceStatus status = listener_->CurrentDeviceStatus();
+ status.battery_status = BatteryStatus::NOT_CHARGING;
+ EXPECT_CALL(mock_observer_, OnDeviceStatusChanged(status))
+ .RetiresOnSaturation();
+ SimulateBatteryChange(true);
+
+ status.battery_status = BatteryStatus::CHARGING;
+ EXPECT_CALL(mock_observer_, OnDeviceStatusChanged(status))
+ .RetiresOnSaturation();
+ SimulateBatteryChange(false);
+ listener_->Stop();
+};
+
+} // namespace
+} // namespace download
diff --git a/chromium/components/download/internal/scheduler/network_listener.cc b/chromium/components/download/internal/scheduler/network_listener.cc
deleted file mode 100644
index d09d52773ee..00000000000
--- a/chromium/components/download/internal/scheduler/network_listener.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/internal/scheduler/network_listener.h"
-
-namespace download {
-
-namespace {
-
-// Converts a ConnectionType to NetworkListener::NetworkStatus.
-NetworkListener::NetworkStatus ToNetworkStatus(
- net::NetworkChangeNotifier::ConnectionType type) {
- switch (type) {
- case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
- case net::NetworkChangeNotifier::CONNECTION_WIFI:
- return NetworkListener::NetworkStatus::UNMETERED;
- case net::NetworkChangeNotifier::CONNECTION_2G:
- case net::NetworkChangeNotifier::CONNECTION_3G:
- case net::NetworkChangeNotifier::CONNECTION_4G:
- return NetworkListener::NetworkStatus::METERED;
- case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
- case net::NetworkChangeNotifier::CONNECTION_NONE:
- case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
- return NetworkListener::NetworkStatus::DISCONNECTED;
- }
- NOTREACHED();
- return NetworkListener::NetworkStatus::DISCONNECTED;
-}
-
-} // namespace
-
-NetworkListener::NetworkListener()
- : status_(NetworkStatus::DISCONNECTED), listening_(false) {}
-
-NetworkListener::~NetworkListener() {
- Stop();
-}
-
-NetworkListener::NetworkStatus NetworkListener::CurrentNetworkStatus() const {
- return ToNetworkStatus(net::NetworkChangeNotifier::GetConnectionType());
-}
-
-void NetworkListener::Start() {
- if (listening_)
- return;
-
- net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
- status_ = ToNetworkStatus(net::NetworkChangeNotifier::GetConnectionType());
- listening_ = true;
-}
-
-void NetworkListener::Stop() {
- if (!listening_)
- return;
-
- net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
- status_ = ToNetworkStatus(net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
- listening_ = false;
-}
-
-void NetworkListener::AddObserver(Observer* observer) {
- observers_.AddObserver(observer);
-}
-
-void NetworkListener::RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
-}
-
-void NetworkListener::OnConnectionTypeChanged(
- net::NetworkChangeNotifier::ConnectionType type) {
- NetworkStatus new_status = ToNetworkStatus(type);
- if (status_ != new_status) {
- status_ = new_status;
- NotifyNetworkChange(status_);
- }
-}
-
-void NetworkListener::NotifyNetworkChange(NetworkStatus network_status) {
- for (auto& observer : observers_)
- observer.OnNetworkChange(network_status);
-}
-
-} // namespace download
diff --git a/chromium/components/download/internal/scheduler/network_listener.h b/chromium/components/download/internal/scheduler/network_listener.h
deleted file mode 100644
index 6b05ffb16b0..00000000000
--- a/chromium/components/download/internal/scheduler/network_listener.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_NETWORK_LISTENER_H_
-#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_NETWORK_LISTENER_H_
-
-#include "net/base/network_change_notifier.h"
-
-namespace download {
-
-// Listens to network status change and notifies observers in download service.
-class NetworkListener
- : public net::NetworkChangeNotifier::ConnectionTypeObserver {
- public:
- // NetworkStatus should mostly one to one map to
- // SchedulingParams::NetworkRequirements. Has coarser granularity than
- // network connection type.
- enum class NetworkStatus {
- DISCONNECTED = 0,
- UNMETERED = 1, // WIFI or Ethernet.
- METERED = 2, // Mobile networks.
- };
-
- class Observer {
- public:
- // Called when network status is changed.
- virtual void OnNetworkChange(NetworkStatus network_status) = 0;
- };
-
- NetworkListener();
- ~NetworkListener() override;
-
- // Returns the current network status for download scheduling.
- NetworkStatus CurrentNetworkStatus() const;
-
- // Starts/stops to listen network change events.
- void Start();
- void Stop();
-
- // Adds/Removes observers.
- void AddObserver(Observer* observer);
- void RemoveObserver(Observer* observer);
-
- private:
- // net::NetworkChangeNotifier implementation.
- void OnConnectionTypeChanged(
- net::NetworkChangeNotifier::ConnectionType type) override;
-
- // Notifies |observers_| about network status change. See |NetworkStatus|.
- void NotifyNetworkChange(NetworkStatus network_status);
-
- // The current network status.
- NetworkStatus status_;
-
- // If we are actively listening to network change events.
- bool listening_;
-
- base::ObserverList<Observer> observers_;
-
- DISALLOW_COPY_AND_ASSIGN(NetworkListener);
-};
-
-} // namespace download
-
-#endif // COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_NETWORK_LISTENER_H_
diff --git a/chromium/components/download/internal/scheduler/network_listener_unittest.cc b/chromium/components/download/internal/scheduler/network_listener_unittest.cc
deleted file mode 100644
index 4b8165dcef1..00000000000
--- a/chromium/components/download/internal/scheduler/network_listener_unittest.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/download/internal/scheduler/network_listener.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ConnectionTypeObserver =
- net::NetworkChangeNotifier::ConnectionTypeObserver;
-using ConnectionType = net::NetworkChangeNotifier::ConnectionType;
-
-namespace download {
-namespace {
-
-// NetworkChangeNotifier that can change network type in tests.
-class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
- public:
- TestNetworkChangeNotifier()
- : net::NetworkChangeNotifier(),
- conn_type_(ConnectionType::CONNECTION_UNKNOWN) {}
-
- // net::NetworkChangeNotifier implementation.
- ConnectionType GetCurrentConnectionType() const override {
- return conn_type_;
- }
-
- // Change the network type.
- void ChangeNetworkType(ConnectionType type) {
- conn_type_ = type;
- net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests(
- type);
- base::RunLoop().RunUntilIdle();
- }
-
- private:
- ConnectionType conn_type_;
-
- DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
-};
-
-class MockObserver : public NetworkListener::Observer {
- public:
- MOCK_METHOD1(OnNetworkChange, void(NetworkListener::NetworkStatus));
-};
-
-class NetworkListenerTest : public testing::Test {
- public:
- // Simulates a network change call.
- void ChangeNetworkType(ConnectionType type) {
- test_network_notifier_.ChangeNetworkType(type);
- }
-
- protected:
- NetworkListener network_listener_;
- MockObserver mock_observer_;
-
- // Needed for network change notifier.
- base::MessageLoop message_loop_;
- TestNetworkChangeNotifier test_network_notifier_;
-};
-
-TEST_F(NetworkListenerTest, NotifyObserverNetworkChange) {
- network_listener_.Start();
- network_listener_.AddObserver(&mock_observer_);
-
- // Initial states check.
- EXPECT_EQ(NetworkListener::NetworkStatus::DISCONNECTED,
- network_listener_.CurrentNetworkStatus());
-
- // Network switch between mobile networks, the observer should be notified
- // only once.
- EXPECT_CALL(mock_observer_,
- OnNetworkChange(NetworkListener::NetworkStatus::METERED))
- .Times(1)
- .RetiresOnSaturation();
-
- ChangeNetworkType(ConnectionType::CONNECTION_4G);
- ChangeNetworkType(ConnectionType::CONNECTION_3G);
- ChangeNetworkType(ConnectionType::CONNECTION_2G);
- EXPECT_EQ(NetworkListener::NetworkStatus::METERED,
- network_listener_.CurrentNetworkStatus());
-
- // Network is switched between wifi and ethernet, the observer should be
- // notified only once.
- EXPECT_CALL(mock_observer_,
- OnNetworkChange(NetworkListener::NetworkStatus::UNMETERED))
- .Times(1)
- .RetiresOnSaturation();
-
- ChangeNetworkType(ConnectionType::CONNECTION_WIFI);
- ChangeNetworkType(ConnectionType::CONNECTION_ETHERNET);
-}
-
-} // namespace
-} // namespace download
diff --git a/chromium/components/download/internal/scheduler/scheduler.h b/chromium/components/download/internal/scheduler/scheduler.h
new file mode 100644
index 00000000000..58d835a3ab3
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/scheduler.h
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_SCHEDULER_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_SCHEDULER_H_
+
+#include "components/download/internal/model.h"
+#include "components/download/internal/scheduler/device_status.h"
+#include "components/download/public/download_params.h"
+
+namespace download {
+
+struct DeviceStatus;
+
+// The interface that talks to download service to schedule platform background
+// download tasks.
+class Scheduler {
+ public:
+ // Reschedule another background platform task based on the scheduling
+ // parameters of |entries|. Should only pass in entries in active or available
+ // state.
+ virtual void Reschedule(const Model::EntryList& entries) = 0;
+
+ // Returns the next download that should be processed based on scheduling
+ // parameters, may return nullptr if no download meets the criteria.
+ // The sequence of polling on entries with exactly same states is undefined.
+ virtual Entry* Next(const Model::EntryList& entries,
+ const DeviceStatus& device_status) = 0;
+
+ virtual ~Scheduler() {}
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_CORE_SCHEDULER_H_
diff --git a/chromium/components/download/internal/scheduler/scheduler_impl.cc b/chromium/components/download/internal/scheduler/scheduler_impl.cc
new file mode 100644
index 00000000000..8ee8f7381f6
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/scheduler_impl.cc
@@ -0,0 +1,131 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/scheduler/scheduler_impl.h"
+
+#include "components/download/internal/client_set.h"
+#include "components/download/internal/config.h"
+#include "components/download/internal/entry_utils.h"
+#include "components/download/internal/scheduler/device_status.h"
+#include "components/download/public/download_params.h"
+#include "components/download/public/task_scheduler.h"
+
+namespace download {
+
+namespace {
+
+// Returns a vector of elements contained in the |set|.
+template <typename T>
+std::vector<T> ToList(const std::set<T>& set) {
+ std::vector<T> list;
+ for (const auto& element : set) {
+ list.push_back(element);
+ }
+ return list;
+}
+
+} // namespace
+
+SchedulerImpl::SchedulerImpl(TaskScheduler* task_scheduler,
+ Configuration* config,
+ const ClientSet* clients)
+ : SchedulerImpl(task_scheduler,
+ config,
+ ToList<DownloadClient>(clients->GetRegisteredClients())) {}
+
+SchedulerImpl::SchedulerImpl(TaskScheduler* task_scheduler,
+ Configuration* config,
+ const std::vector<DownloadClient>& clients)
+ : task_scheduler_(task_scheduler),
+ config_(config),
+ download_clients_(clients),
+ current_client_index_(0) {
+ DCHECK(task_scheduler_);
+}
+
+SchedulerImpl::~SchedulerImpl() = default;
+
+void SchedulerImpl::Reschedule(const Model::EntryList& entries) {
+ if (entries.empty()) {
+ task_scheduler_->CancelTask(DownloadTaskType::DOWNLOAD_TASK);
+ return;
+ }
+
+ // TODO(xingliu): Support NetworkRequirements::OPTIMISTIC.
+ task_scheduler_->CancelTask(DownloadTaskType::DOWNLOAD_TASK);
+
+ Criteria criteria = util::GetSchedulingCriteria(entries);
+ task_scheduler_->ScheduleTask(
+ DownloadTaskType::DOWNLOAD_TASK, criteria.requires_unmetered_network,
+ criteria.requires_battery_charging,
+ base::saturated_cast<long>(config_->window_start_time.InSeconds()),
+ base::saturated_cast<long>(config_->window_end_time.InSeconds()));
+}
+
+Entry* SchedulerImpl::Next(const Model::EntryList& entries,
+ const DeviceStatus& device_status) {
+ std::map<DownloadClient, Entry*> candidates =
+ FindCandidates(entries, device_status);
+
+ Entry* entry = nullptr;
+ size_t index = current_client_index_;
+
+ // Finds the next entry to download.
+ for (size_t i = 0; i < download_clients_.size(); ++i) {
+ DownloadClient client =
+ download_clients_[(index + i) % download_clients_.size()];
+ Entry* candidate = candidates[client];
+
+ // Some clients may have no entries, continue to check other clients.
+ if (!candidate)
+ continue;
+
+ bool ui_priority =
+ candidate->scheduling_params.priority == SchedulingParams::Priority::UI;
+
+ // Records the first available candidate. Keep iterating to see if there
+ // are UI priority entries for other clients.
+ if (!entry || ui_priority) {
+ entry = candidate;
+
+ // Load balancing between clients.
+ current_client_index_ = (index + i + 1) % download_clients_.size();
+
+ // UI priority entry will be processed immediately.
+ if (ui_priority)
+ break;
+ }
+ }
+ return entry;
+}
+
+std::map<DownloadClient, Entry*> SchedulerImpl::FindCandidates(
+ const Model::EntryList& entries,
+ const DeviceStatus& device_status) {
+ std::map<DownloadClient, Entry*> candidates;
+
+ if (entries.empty())
+ return candidates;
+
+ for (auto* const entry : entries) {
+ DCHECK(entry);
+ const SchedulingParams& current_params = entry->scheduling_params;
+
+ // Every download needs to pass the state and device status check.
+ if (entry->state != Entry::State::AVAILABLE ||
+ !device_status.MeetsCondition(current_params).MeetsRequirements()) {
+ continue;
+ }
+
+ // Find the most appropriate download based on priority and cancel time.
+ Entry* candidate = candidates[entry->client];
+ if (!candidate || util::EntryBetterThan(*entry, *candidate)) {
+ candidates[entry->client] = entry;
+ }
+ }
+
+ return candidates;
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/scheduler/scheduler_impl.h b/chromium/components/download/internal/scheduler/scheduler_impl.h
new file mode 100644
index 00000000000..1284d38fe0b
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/scheduler_impl.h
@@ -0,0 +1,72 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_SCHEDULER_IMPL_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_SCHEDULER_IMPL_H_
+
+#include "components/download/internal/scheduler/scheduler.h"
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/download/internal/entry.h"
+
+namespace download {
+
+class ClientSet;
+class TaskScheduler;
+struct Configuration;
+
+// Scheduler implementation that
+// 1. Creates platform background task based on the states of download entries.
+// 2. Polls the next entry to be processed by the service mainly according to
+// scheduling parameters and current device status.
+//
+// Provides load balancing between download clients using the service.
+class SchedulerImpl : public Scheduler {
+ public:
+ SchedulerImpl(TaskScheduler* task_scheduler,
+ Configuration* config,
+ const ClientSet* clients);
+ SchedulerImpl(TaskScheduler* task_scheduler,
+ Configuration* config,
+ const std::vector<DownloadClient>& clients);
+ ~SchedulerImpl() override;
+
+ // Scheduler implementation.
+ void Reschedule(const Model::EntryList& entries) override;
+ Entry* Next(const Model::EntryList& entries,
+ const DeviceStatus& device_status) override;
+
+ private:
+ // Finds a candidate for each download client to be processed next by the
+ // service.
+ // The candidates are selected based on scheduling parameters and current
+ // device status.
+ std::map<DownloadClient, Entry*> FindCandidates(
+ const Model::EntryList& entries,
+ const DeviceStatus& device_status);
+
+ // Used to create platform dependent background tasks.
+ TaskScheduler* task_scheduler_;
+
+ // Download service configuration.
+ Configuration* config_;
+
+ // List of all download client id, used in round robin load balancing.
+ // Downloads will be delivered to clients with incremental order based on
+ // the index of this list.
+ const std::vector<DownloadClient> download_clients_;
+
+ // The index of the current client.
+ // See |download_clients_|.
+ size_t current_client_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchedulerImpl);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_SCHEDULER_SCHEDULER_IMPL_H_
diff --git a/chromium/components/download/internal/scheduler/scheduler_impl_unittest.cc b/chromium/components/download/internal/scheduler/scheduler_impl_unittest.cc
new file mode 100644
index 00000000000..482d8141805
--- /dev/null
+++ b/chromium/components/download/internal/scheduler/scheduler_impl_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/scheduler/scheduler_impl.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/download/internal/config.h"
+#include "components/download/internal/entry.h"
+#include "components/download/internal/scheduler/device_status.h"
+#include "components/download/public/task_scheduler.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::InSequence;
+
+namespace download {
+namespace {
+
+class MockTaskScheduler : public TaskScheduler {
+ public:
+ MockTaskScheduler() = default;
+ ~MockTaskScheduler() override = default;
+
+ MOCK_METHOD5(ScheduleTask, void(DownloadTaskType, bool, bool, long, long));
+ MOCK_METHOD1(CancelTask, void(DownloadTaskType));
+};
+
+class DownloadSchedulerImplTest : public testing::Test {
+ public:
+ DownloadSchedulerImplTest() {}
+ ~DownloadSchedulerImplTest() override = default;
+
+ void TearDown() override { DestroyScheduler(); }
+
+ void BuildScheduler(const std::vector<DownloadClient> clients) {
+ scheduler_ =
+ base::MakeUnique<SchedulerImpl>(&task_scheduler_, &config_, clients);
+ }
+ void DestroyScheduler() { scheduler_.reset(); }
+
+ // Helper function to create a list of entries for the scheduler to query the
+ // next entry.
+ void BuildDataEntries(size_t size) {
+ entries_ = std::vector<Entry>(size, Entry());
+ for (size_t i = 0; i < size; ++i) {
+ entries_[i].guid = base::IntToString(i);
+ entries_[i].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ entries_[i].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+ entries_[i].state = Entry::State::AVAILABLE;
+ }
+ }
+
+ // Returns list of entry pointers to feed to the scheduler.
+ Model::EntryList entries() {
+ Model::EntryList entry_list;
+ for (auto& entry : entries_) {
+ entry_list.emplace_back(&entry);
+ }
+ return entry_list;
+ }
+
+ // Simulates the entry has been processed by the download service and the
+ // state has changed.
+ void MakeEntryActive(Entry* entry) {
+ if (entry)
+ entry->state = Entry::State::ACTIVE;
+ }
+
+ // Reverts the states of entry so that the scheduler can poll it again.
+ void MakeEntryAvailable(Entry* entry) {
+ entry->state = Entry::State::AVAILABLE;
+ }
+
+ // Helper function to build a device status.
+ DeviceStatus BuildDeviceStatus(BatteryStatus battery, NetworkStatus network) {
+ DeviceStatus device_status;
+ device_status.battery_status = battery;
+ device_status.network_status = network;
+ return device_status;
+ }
+
+ protected:
+ std::unique_ptr<SchedulerImpl> scheduler_;
+ MockTaskScheduler task_scheduler_;
+ Configuration config_;
+
+ // Entries owned by the test fixture.
+ std::vector<Entry> entries_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadSchedulerImplTest);
+};
+
+// Ensures normal polling logic is correct.
+TEST_F(DownloadSchedulerImplTest, BasicPolling) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST_2,
+ DownloadClient::TEST});
+
+ // Client TEST: entry 0.
+ // Client TEST_2: entry 1.
+ // Poll sequence: 1 -> 0.
+ BuildDataEntries(2);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[1].client = DownloadClient::TEST_2;
+
+ // First download belongs to first client.
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(next, &entries_[1]);
+ MakeEntryActive(next);
+
+ // If the first one is processed, the next should be the other entry.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(next, &entries_[0]);
+ MakeEntryActive(next);
+}
+
+// Tests the load balancing and polling downloads based on cancel time.
+TEST_F(DownloadSchedulerImplTest, BasicLoadBalancing) {
+ BuildScheduler(std::vector<DownloadClient>{
+ DownloadClient::TEST, DownloadClient::TEST_2, DownloadClient::TEST_3});
+
+ // Client TEST: entry 0, entry 1 (earlier cancel time).
+ // Client TEST_2: entry 2.
+ // Client TEST_3: No entries.
+ // Poll sequence: 1 -> 2 -> 0.
+ BuildDataEntries(3);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
+ entries_[1].client = DownloadClient::TEST;
+ entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(10);
+ entries_[2].client = DownloadClient::TEST_2;
+ entries_[2].scheduling_params.cancel_time = base::Time::FromInternalValue(30);
+
+ // There are 2 downloads for client 0, the one with earlier create time will
+ // be the next download.
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[1], next);
+ MakeEntryActive(next);
+
+ // The second download should belongs to client 1, because of the round robin
+ // load balancing.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[2], next);
+ MakeEntryActive(next);
+
+ // Only one entry left, which will be the next.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ MakeEntryActive(next);
+
+ // Keep polling twice, since no available downloads, both will return nullptr.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(nullptr, next);
+ MakeEntryActive(next);
+
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(nullptr, next);
+ MakeEntryActive(next);
+}
+
+// Ensures downloads are polled based on scheduling parameters and device
+// status.
+TEST_F(DownloadSchedulerImplTest, SchedulingParams) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
+ BuildDataEntries(1);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ entries_[0].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+
+ Entry* next = nullptr;
+
+ // Tests network scheduling parameter.
+ // No downloads can be polled when network disconnected.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::DISCONNECTED));
+ EXPECT_EQ(nullptr, next);
+
+ // If the network is metered, and scheduling parameter requires unmetered
+ // network, the download should not be polled.
+ next = scheduler_->Next(entries(), BuildDeviceStatus(BatteryStatus::CHARGING,
+ NetworkStatus::METERED));
+ EXPECT_EQ(nullptr, next);
+
+ // If the network requirement is none, the download can happen under metered
+ // network. However, download won't happen when network is disconnected.
+ entries_[0].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::NONE;
+ next = scheduler_->Next(entries(), BuildDeviceStatus(BatteryStatus::CHARGING,
+ NetworkStatus::METERED));
+ EXPECT_EQ(&entries_[0], next);
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::DISCONNECTED));
+ EXPECT_EQ(nullptr, next);
+ MakeEntryActive(next);
+
+ // Tests battery sensitive scheduling parameter.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(nullptr, next);
+ MakeEntryActive(next);
+
+ entries_[0].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ MakeEntryActive(next);
+}
+
+// Ensures higher priority will be scheduled first.
+TEST_F(DownloadSchedulerImplTest, Priority) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
+
+ // The second entry has higher priority but is created later than the first
+ // entry. This ensures priority is checked before the create time.
+ BuildDataEntries(2);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
+ entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
+ entries_[1].client = DownloadClient::TEST;
+ entries_[1].scheduling_params.priority = SchedulingParams::Priority::HIGH;
+ entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(40);
+
+ // Download with higher priority should be polled first, even if there is
+ // another download created earlier.
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[1], next);
+
+ // Download with non UI priority should be subject to network and battery
+ // scheduling parameters. The higher priority one will be ignored because of
+ // mismatching battery condition.
+ entries_[1].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ entries_[0].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ MakeEntryActive(next);
+}
+
+// Ensures UI priority entries are subject to device status check.
+TEST_F(DownloadSchedulerImplTest, UIPrioritySubjectToDeviceStatus) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
+ DownloadClient::TEST_2});
+
+ // Client TEST: entry 0.
+ // Client TEST_2: entry 1 (UI priority, cancel later).
+ BuildDataEntries(2);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
+ entries_[1].client = DownloadClient::TEST_2;
+ entries_[1].scheduling_params.priority = SchedulingParams::Priority::UI;
+
+ // UI priority is also subject to device status validation.
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::NOT_CHARGING, NetworkStatus::METERED));
+ EXPECT_EQ(nullptr, next);
+ MakeEntryActive(next);
+}
+
+// UI priority entries will be processed first even if they doesn't belong to
+// the current client in load balancing.
+TEST_F(DownloadSchedulerImplTest, UIPriorityLoadBalancing) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
+ DownloadClient::TEST_2});
+
+ // Client TEST: entry 0(Low priority).
+ // Client TEST_2: entry 1(UI priority).
+ BuildDataEntries(2);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.priority = SchedulingParams::Priority::LOW;
+ entries_[1].client = DownloadClient::TEST_2;
+ entries_[1].scheduling_params.priority = SchedulingParams::Priority::UI;
+
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[1], next);
+ MakeEntryActive(next);
+}
+
+// When multiple UI priority entries exist, the next entry is selected based on
+// cancel time and load balancing.
+TEST_F(DownloadSchedulerImplTest, MultipleUIPriorityEntries) {
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST,
+ DownloadClient::TEST_2});
+ BuildDataEntries(4);
+
+ // Client TEST: entry 0(UI priority), entry 1(UI priority, early cancel time).
+ // Client TEST_2: entry 2(UI priority), entry 3(high priority, early cancel
+ // time). Poll sequence: 1 -> 2 -> 0 -> 3.
+ for (auto& entry : entries_) {
+ entry.scheduling_params.priority = SchedulingParams::Priority::UI;
+ }
+ entries_[0].client = DownloadClient::TEST;
+ entries_[0].scheduling_params.cancel_time = base::Time::FromInternalValue(40);
+ entries_[1].client = DownloadClient::TEST;
+ entries_[1].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
+ entries_[2].client = DownloadClient::TEST_2;
+ entries_[2].scheduling_params.cancel_time = base::Time::FromInternalValue(50);
+ entries_[3].client = DownloadClient::TEST_2;
+ entries_[3].scheduling_params.cancel_time = base::Time::FromInternalValue(20);
+ entries_[3].scheduling_params.priority = SchedulingParams::Priority::HIGH;
+
+ // When device conditions are meet, UI priority entry with the earliest cancel
+ // time will be processed first.
+ Entry* next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[1], next);
+ MakeEntryActive(next);
+
+ // Next entry will be UI priority entry from another client.
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[2], next);
+ MakeEntryActive(next);
+
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[0], next);
+ MakeEntryActive(next);
+
+ next = scheduler_->Next(
+ entries(),
+ BuildDeviceStatus(BatteryStatus::CHARGING, NetworkStatus::UNMETERED));
+ EXPECT_EQ(&entries_[3], next);
+ MakeEntryActive(next);
+}
+
+// Ensures the reschedule logic works correctly, and we can pass the correct
+// criteria to platform task scheduler.
+TEST_F(DownloadSchedulerImplTest, Reschedule) {
+ InSequence s;
+
+ BuildScheduler(std::vector<DownloadClient>{DownloadClient::TEST});
+ BuildDataEntries(2);
+ entries_[0].client = DownloadClient::TEST;
+ entries_[1].client = DownloadClient::TEST;
+
+ entries_[0].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ entries_[0].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+ entries_[1].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_SENSITIVE;
+ entries_[1].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::UNMETERED;
+
+ Criteria criteria;
+ EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
+ .RetiresOnSaturation();
+ EXPECT_CALL(task_scheduler_,
+ ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
+ criteria.requires_unmetered_network,
+ criteria.requires_battery_charging, _, _))
+ .RetiresOnSaturation();
+ scheduler_->Reschedule(entries());
+
+ entries_[0].scheduling_params.battery_requirements =
+ SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
+ criteria.requires_battery_charging = false;
+ EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
+ .RetiresOnSaturation();
+ EXPECT_CALL(task_scheduler_,
+ ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
+ criteria.requires_unmetered_network,
+ criteria.requires_battery_charging, _, _))
+ .RetiresOnSaturation();
+ scheduler_->Reschedule(entries());
+
+ entries_[0].scheduling_params.network_requirements =
+ SchedulingParams::NetworkRequirements::NONE;
+ criteria.requires_unmetered_network = false;
+ EXPECT_CALL(task_scheduler_, CancelTask(DownloadTaskType::DOWNLOAD_TASK))
+ .RetiresOnSaturation();
+ EXPECT_CALL(task_scheduler_,
+ ScheduleTask(DownloadTaskType::DOWNLOAD_TASK,
+ criteria.requires_unmetered_network,
+ criteria.requires_battery_charging, _, _))
+ .RetiresOnSaturation();
+ scheduler_->Reschedule(entries());
+}
+
+} // namespace
+} // namespace download
diff --git a/chromium/components/download/internal/service_config_impl.cc b/chromium/components/download/internal/service_config_impl.cc
new file mode 100644
index 00000000000..bb0e5ec0040
--- /dev/null
+++ b/chromium/components/download/internal/service_config_impl.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/service_config_impl.h"
+
+#include "base/time/time.h"
+#include "components/download/internal/config.h"
+
+namespace download {
+
+ServiceConfigImpl::ServiceConfigImpl(Configuration* config) : config_(config) {}
+
+ServiceConfigImpl::~ServiceConfigImpl() = default;
+
+uint32_t ServiceConfigImpl::GetMaxScheduledDownloadsPerClient() const {
+ return config_->max_scheduled_downloads;
+}
+
+const base::TimeDelta& ServiceConfigImpl::GetFileKeepAliveTime() const {
+ return config_->file_keep_alive_time;
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/service_config_impl.h b/chromium/components/download/internal/service_config_impl.h
new file mode 100644
index 00000000000..15da514166e
--- /dev/null
+++ b/chromium/components/download/internal/service_config_impl.h
@@ -0,0 +1,32 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_SERVICE_CONFIG_IMPL_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_SERVICE_CONFIG_IMPL_H_
+
+#include "base/macros.h"
+#include "components/download/public/service_config.h"
+
+namespace download {
+
+struct Configuration;
+
+class ServiceConfigImpl : public ServiceConfig {
+ public:
+ explicit ServiceConfigImpl(Configuration* config);
+ ~ServiceConfigImpl() override;
+
+ // ServiceConfig implementation.
+ uint32_t GetMaxScheduledDownloadsPerClient() const override;
+ const base::TimeDelta& GetFileKeepAliveTime() const override;
+
+ private:
+ struct Configuration* config_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceConfigImpl);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_SERVICE_CONFIG_IMPL_H_
diff --git a/chromium/components/download/internal/service_config_impl_unittest.cc b/chromium/components/download/internal/service_config_impl_unittest.cc
new file mode 100644
index 00000000000..e093dec9de3
--- /dev/null
+++ b/chromium/components/download/internal/service_config_impl_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/service_config_impl.h"
+#include "components/download/internal/config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace download {
+
+TEST(ServiceConfigImplTest, TestApi) {
+ Configuration config;
+ ServiceConfigImpl impl(&config);
+
+ config.max_scheduled_downloads = 7;
+ config.file_keep_alive_time = base::TimeDelta::FromSeconds(12);
+
+ EXPECT_EQ(config.max_scheduled_downloads,
+ impl.GetMaxScheduledDownloadsPerClient());
+ EXPECT_EQ(config.file_keep_alive_time, impl.GetFileKeepAliveTime());
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/startup_status.cc b/chromium/components/download/internal/startup_status.cc
new file mode 100644
index 00000000000..393e289a6a3
--- /dev/null
+++ b/chromium/components/download/internal/startup_status.cc
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/startup_status.h"
+
+namespace download {
+
+StartupStatus::StartupStatus() = default;
+StartupStatus::~StartupStatus() = default;
+
+void StartupStatus::Reset() {
+ driver_ok.reset();
+ model_ok.reset();
+ file_monitor_ok.reset();
+}
+
+bool StartupStatus::Complete() const {
+ return driver_ok.has_value() && model_ok.has_value() &&
+ file_monitor_ok.has_value();
+}
+
+bool StartupStatus::Ok() const {
+ DCHECK(Complete());
+ return driver_ok.value() && model_ok.value() && file_monitor_ok.value();
+}
+
+} // namespace download
diff --git a/chromium/components/download/internal/startup_status.h b/chromium/components/download/internal/startup_status.h
new file mode 100644
index 00000000000..754428afad9
--- /dev/null
+++ b/chromium/components/download/internal/startup_status.h
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_STARTUP_STATUS_H_
+#define COMPONENTS_DOWNLOAD_STARTUP_STATUS_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+
+namespace download {
+
+// Helper struct to track the initialization status of various Controller
+// internal components.
+struct StartupStatus {
+ StartupStatus();
+ ~StartupStatus();
+
+ base::Optional<bool> driver_ok;
+ base::Optional<bool> model_ok;
+ base::Optional<bool> file_monitor_ok;
+
+ // Resets all startup state tracking.
+ void Reset();
+
+ // Whether or not all components have finished initialization. Note that this
+ // does not mean that all components were initialized successfully.
+ bool Complete() const;
+
+ // Whether or not all components have initialized successfully. Should only
+ // be called if Complete() is true.
+ bool Ok() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StartupStatus);
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_STARTUP_STATUS_H_
diff --git a/chromium/components/download/internal/stats.cc b/chromium/components/download/internal/stats.cc
new file mode 100644
index 00000000000..5b4f12c52a3
--- /dev/null
+++ b/chromium/components/download/internal/stats.cc
@@ -0,0 +1,310 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/download/internal/stats.h"
+
+#include <map>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/download/internal/startup_status.h"
+
+namespace download {
+namespace stats {
+namespace {
+
+// The maximum tracked file size in KB, larger files will fall into overflow
+// bucket.
+const int64_t kMaxFileSizeKB = 4 * 1024 * 1024; /* 4GB */
+
+// Converts DownloadTaskType to histogram suffix.
+// Should maps to suffix string in histograms.xml.
+std::string TaskTypeToHistogramSuffix(DownloadTaskType task_type) {
+ switch (task_type) {
+ case DownloadTaskType::DOWNLOAD_TASK:
+ return "DownloadTask";
+ case DownloadTaskType::CLEANUP_TASK:
+ return "CleanUpTask";
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+// Converts Entry::State to histogram suffix.
+// Should maps to suffix string in histograms.xml.
+std::string EntryStateToHistogramSuffix(Entry::State state) {
+ std::string suffix;
+ switch (state) {
+ case Entry::State::NEW:
+ return "New";
+ case Entry::State::AVAILABLE:
+ return "Available";
+ case Entry::State::ACTIVE:
+ return "Active";
+ case Entry::State::PAUSED:
+ return "Paused";
+ case Entry::State::COMPLETE:
+ return "Complete";
+ case Entry::State::COUNT:
+ break;
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+// Converts DownloadClient to histogram suffix.
+// Should maps to suffix string in histograms.xml.
+std::string ClientToHistogramSuffix(DownloadClient client) {
+ switch (client) {
+ case DownloadClient::TEST:
+ case DownloadClient::TEST_2:
+ case DownloadClient::TEST_3:
+ case DownloadClient::INVALID:
+ return "__Test__";
+ case DownloadClient::OFFLINE_PAGE_PREFETCH:
+ return "OfflinePage";
+ case DownloadClient::BOUNDARY:
+ NOTREACHED();
+ break;
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+// Converts CompletionType to histogram suffix.
+// Should maps to suffix string in histograms.xml.
+std::string CompletionTypeToHistogramSuffix(CompletionType type) {
+ switch (type) {
+ case CompletionType::SUCCEED:
+ return "Succeed";
+ case CompletionType::FAIL:
+ return "Fail";
+ case CompletionType::ABORT:
+ return "Abort";
+ case CompletionType::TIMEOUT:
+ return "Timeout";
+ case CompletionType::UNKNOWN:
+ return "Unknown";
+ case CompletionType::CANCEL:
+ return "Cancel";
+ case CompletionType::COUNT:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+// Converts FileCleanupReason to histogram suffix.
+// Should maps to suffix string in histograms.xml.
+std::string FileCleanupReasonToHistogramSuffix(FileCleanupReason reason) {
+ switch (reason) {
+ case FileCleanupReason::TIMEOUT:
+ return "Timeout";
+ case FileCleanupReason::ORPHANED:
+ return "Orphaned";
+ case FileCleanupReason::UNKNOWN:
+ return "Unknown";
+ case FileCleanupReason::HARD_RECOVERY:
+ return "HardRecovery";
+ case FileCleanupReason::COUNT:
+ NOTREACHED();
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+// Helper method to log StartUpResult.
+void LogStartUpResult(bool in_recovery, StartUpResult result) {
+ if (in_recovery) {
+ base::UmaHistogramEnumeration("Download.Service.StartUpStatus.Recovery",
+ result, StartUpResult::COUNT);
+ } else {
+ base::UmaHistogramEnumeration(
+ "Download.Service.StartUpStatus.Initialization", result,
+ StartUpResult::COUNT);
+ }
+}
+
+// Helper method to log the number of entries under a particular state.
+void LogDatabaseRecords(Entry::State state, uint32_t record_count) {
+ std::string name("Download.Service.Db.Records");
+ name.append(".").append(EntryStateToHistogramSuffix(state));
+ base::UmaHistogramCustomCounts(name, record_count, 1, 500, 50);
+}
+
+} // namespace
+
+void LogControllerStartupStatus(bool in_recovery, const StartupStatus& status) {
+ DCHECK(status.Complete());
+
+ // Total counts for general success/failure rate.
+ LogStartUpResult(in_recovery, status.Ok() ? StartUpResult::SUCCESS
+ : StartUpResult::FAILURE);
+
+ // Failure reasons.
+ if (!status.driver_ok.value())
+ LogStartUpResult(in_recovery, StartUpResult::FAILURE_REASON_DRIVER);
+ if (!status.model_ok.value())
+ LogStartUpResult(in_recovery, StartUpResult::FAILURE_REASON_MODEL);
+ if (!status.file_monitor_ok.value())
+ LogStartUpResult(in_recovery, StartUpResult::FAILURE_REASON_FILE_MONITOR);
+}
+
+void LogServiceApiAction(DownloadClient client, ServiceApiAction action) {
+ // Total count for each action.
+ std::string name("Download.Service.Request.ClientAction");
+ base::UmaHistogramEnumeration(name, action, ServiceApiAction::COUNT);
+
+ // Total count for each action with client suffix.
+ name.append(".").append(ClientToHistogramSuffix(client));
+ base::UmaHistogramEnumeration(name, action, ServiceApiAction::COUNT);
+}
+
+void LogStartDownloadResult(DownloadClient client,
+ DownloadParams::StartResult result) {
+ // Total count for each start result.
+ std::string name("Download.Service.Request.StartResult");
+ base::UmaHistogramEnumeration(name, result,
+ DownloadParams::StartResult::COUNT);
+
+ // Total count for each client result with client suffix.
+ name.append(".").append(ClientToHistogramSuffix(client));
+ base::UmaHistogramEnumeration(name, result,
+ DownloadParams::StartResult::COUNT);
+}
+
+void LogStartDownloadResponse(DownloadClient client,
+ Client::ShouldDownload should_download) {
+ // Total count for each start response.
+ std::string name("Download.Service.Request.StartResponse");
+ base::UmaHistogramEnumeration(name, should_download,
+ Client::ShouldDownload::COUNT);
+
+ // Total count for each client response with client suffix.
+ name.append(".").append(ClientToHistogramSuffix(client));
+ base::UmaHistogramEnumeration(name, should_download,
+ Client::ShouldDownload::COUNT);
+}
+
+void LogDownloadParams(const DownloadParams& params) {
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Request.BatteryRequirement",
+ params.scheduling_params.battery_requirements,
+ SchedulingParams::BatteryRequirements::COUNT);
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Request.NetworkRequirement",
+ params.scheduling_params.network_requirements,
+ SchedulingParams::NetworkRequirements::COUNT);
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Request.Priority",
+ params.scheduling_params.priority,
+ SchedulingParams::Priority::COUNT);
+}
+
+void LogRecoveryOperation(Entry::State to_state) {
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Recovery", to_state,
+ Entry::State::COUNT);
+}
+
+void LogDownloadCompletion(CompletionType type,
+ const base::TimeDelta& time_span,
+ uint64_t file_size_bytes) {
+ // Records completion type.
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Finish.Type", type,
+ CompletionType::COUNT);
+
+ // TODO(xingliu): Use DownloadItem::GetStartTime and DownloadItem::GetEndTime
+ // to record the completion time to histogram "Download.Service.Finish.Time".
+ // Also propagates and records the mime type here.
+
+ // Records the file size.
+ std::string name("Download.Service.Finish.FileSize");
+ uint64_t file_size_kb = file_size_bytes / 1024;
+ base::UmaHistogramCustomCounts(name, file_size_kb, 1, kMaxFileSizeKB, 50);
+
+ name.append(".").append(CompletionTypeToHistogramSuffix(type));
+ base::UmaHistogramCustomCounts(name, file_size_kb, 1, kMaxFileSizeKB, 50);
+}
+
+void LogModelOperationResult(ModelAction action, bool success) {
+ if (success) {
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Db.Operation.Success", action,
+ ModelAction::COUNT);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Db.Operation.Failure", action,
+ ModelAction::COUNT);
+ }
+}
+
+void LogEntries(std::map<Entry::State, uint32_t>& entries_count) {
+ uint32_t total_records = 0;
+ for (const auto& entry_count : entries_count)
+ total_records += entry_count.second;
+
+ // Total number of records in database.
+ base::UmaHistogramCustomCounts("Download.Service.Db.Records", total_records,
+ 1, 500, 50);
+
+ // Number of records for each Entry::State.
+ for (Entry::State state = Entry::State::NEW; state != Entry::State::COUNT;
+ state = (Entry::State)((int)(state) + 1)) {
+ LogDatabaseRecords(state, entries_count[state]);
+ }
+}
+
+void LogScheduledTaskStatus(DownloadTaskType task_type,
+ ScheduledTaskStatus status) {
+ std::string name("Download.Service.TaskScheduler.Status");
+ base::UmaHistogramEnumeration(name, status, ScheduledTaskStatus::COUNT);
+
+ name.append(".").append(TaskTypeToHistogramSuffix(task_type));
+ base::UmaHistogramEnumeration(name, status, ScheduledTaskStatus::COUNT);
+}
+
+void LogsFileDirectoryCreationError(base::File::Error error) {
+ // Maps to histogram enum PlatformFileError.
+ UMA_HISTOGRAM_ENUMERATION("Download.Service.Files.DirCreationError", -error,
+ -base::File::Error::FILE_ERROR_MAX);
+}
+
+void LogFileCleanupStatus(FileCleanupReason reason,
+ int succeeded_cleanups,
+ int failed_cleanups,
+ int external_cleanups) {
+ std::string name("Download.Service.Files.CleanUp.Success");
+ base::UmaHistogramCounts100(name, succeeded_cleanups);
+ name.append(".").append(FileCleanupReasonToHistogramSuffix(reason));
+ base::UmaHistogramCounts100(name, succeeded_cleanups);
+
+ name = "Download.Service.Files.CleanUp.Failure";
+ base::UmaHistogramCounts100(name, failed_cleanups);
+ name.append(".").append(FileCleanupReasonToHistogramSuffix(reason));
+ base::UmaHistogramCounts100(name, failed_cleanups);
+
+ name = "Download.Service.Files.CleanUp.External";
+ base::UmaHistogramCounts100(name, external_cleanups);
+ name.append(".").append(FileCleanupReasonToHistogramSuffix(reason));
+ base::UmaHistogramCounts100(name, external_cleanups);
+}
+
+void LogFileLifeTime(const base::TimeDelta& file_life_time) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Download.Service.Files.LifeTime", file_life_time,
+ base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromDays(8), 100);
+}
+
+void LogFileDirDiskUtilization(int64_t total_disk_space,
+ int64_t free_disk_space,
+ int64_t files_size) {
+ UMA_HISTOGRAM_PERCENTAGE("Download.Service.Files.FreeDiskSpace",
+ (free_disk_space * 100) / total_disk_space);
+ UMA_HISTOGRAM_PERCENTAGE("Download.Service.Files.DiskUsed",
+ (files_size * 100) / total_disk_space);
+}
+
+void LogFilePathRenamed(bool renamed) {
+ UMA_HISTOGRAM_BOOLEAN("Download.Service.Files.PathRenamed", renamed);
+}
+
+} // namespace stats
+} // namespace download
diff --git a/chromium/components/download/internal/stats.h b/chromium/components/download/internal/stats.h
new file mode 100644
index 00000000000..e100d35189d
--- /dev/null
+++ b/chromium/components/download/internal/stats.h
@@ -0,0 +1,191 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_INTERNAL_STATS_H_
+#define COMPONENTS_DOWNLOAD_INTERNAL_STATS_H_
+
+#include "base/files/file.h"
+#include "components/download/internal/controller.h"
+#include "components/download/internal/entry.h"
+#include "components/download/public/clients.h"
+#include "components/download/public/download_params.h"
+#include "components/download/public/download_task_types.h"
+
+namespace download {
+
+struct StartupStatus;
+
+namespace stats {
+
+// Please follow the following rules for all enums:
+// 1. Keep them in sync with the corresponding entry in enums.xml.
+// 2. Treat them as append only.
+// 3. Do not remove any enums. Only mark them as deprecated.
+
+// Enum used to track download service start up result and failure reasons.
+// Most of the fields should map to StartupStatus.
+// Failure reasons are not mutually exclusive.
+enum class StartUpResult {
+ // Download service successfully started.
+ SUCCESS = 0,
+
+ // Download service start up failed.
+ FAILURE = 1,
+
+ // Download driver is not ready.
+ FAILURE_REASON_DRIVER = 2,
+
+ // Database layer failed to initialized.
+ FAILURE_REASON_MODEL = 3,
+
+ // File monitor failed to start.
+ FAILURE_REASON_FILE_MONITOR = 4,
+
+ // The count of entries for the enum.
+ COUNT = 5,
+};
+
+// Enum used by UMA metrics to track which actions a Client is taking on the
+// service.
+enum class ServiceApiAction {
+ // Represents a call to DownloadService::StartDownload.
+ START_DOWNLOAD = 0,
+
+ // Represents a call to DownloadService::PauseDownload.
+ PAUSE_DOWNLOAD = 1,
+
+ // Represents a call to DownloadService::ResumeDownload.
+ RESUME_DOWNLOAD = 2,
+
+ // Represents a call to DownloadService::CancelDownload.
+ CANCEL_DOWNLOAD = 3,
+
+ // Represents a call to DownloadService::ChangeCriteria.
+ CHANGE_CRITERIA = 4,
+
+ // The count of entries for the enum.
+ COUNT = 5,
+};
+
+// Enum used by UMA metrics to tie to specific actions taken on a Model. This
+// can be used to track failure events.
+enum class ModelAction {
+ // Represents an attempt to initialize the Model.
+ INITIALIZE = 0,
+
+ // Represents an attempt to add an Entry to the Model.
+ ADD = 1,
+
+ // Represents an attempt to update an Entry in the Model.
+ UPDATE = 2,
+
+ // Represents an attempt to remove an Entry from the Model.
+ REMOVE = 3,
+
+ // The count of entries for the enum.
+ COUNT = 4,
+};
+
+// Enum used by UMA metrics to log the status of scheduled tasks.
+enum class ScheduledTaskStatus {
+ // Startup failed and the task was not run.
+ ABORTED_ON_FAILED_INIT = 0,
+
+ // OnStopScheduledTask() was received before the task could be fired.
+ CANCELLED_ON_STOP = 1,
+
+ // Callback was run successfully after completion of the task.
+ COMPLETED_NORMALLY = 2,
+
+ // The count of entries for the enum.
+ COUNT = 3,
+};
+
+// Enum used by UMA metrics to track various types of cleanup actions taken by
+// the service.
+enum class FileCleanupReason {
+ // The file was deleted by the service after timeout.
+ TIMEOUT = 0,
+
+ // The database entry for the file was found not associated with any
+ // registered client.
+ ORPHANED = 1,
+
+ // At startup, the file was found not being associated with any model entry or
+ // driver entry.
+ UNKNOWN = 2,
+
+ // We're trying to remove all files as part of a hard recovery attempt.
+ HARD_RECOVERY = 3,
+
+ // The count of entries for the enum.
+ COUNT = 4,
+};
+
+// Logs the results of starting up the Controller. Will log each failure reason
+// if |status| contains more than one initialization failure.
+void LogControllerStartupStatus(bool in_recovery, const StartupStatus& status);
+
+// Logs an action taken on the service API.
+void LogServiceApiAction(DownloadClient client, ServiceApiAction action);
+
+// Logs the result of a StartDownload() attempt on the service.
+void LogStartDownloadResult(DownloadClient client,
+ DownloadParams::StartResult result);
+
+// Logs the client response to StartDownload() attempt on the service.
+void LogStartDownloadResponse(DownloadClient client,
+ download::Client::ShouldDownload should_download);
+
+// Logs the download parameters when StartDownload() is called.
+void LogDownloadParams(const DownloadParams& params);
+
+// Logs recovery operations that happened when we had to move from one state
+// to another on startup.
+void LogRecoveryOperation(Entry::State to_state);
+
+// Logs download completion event, download time, and the file size.
+void LogDownloadCompletion(CompletionType type,
+ const base::TimeDelta& time_span,
+ uint64_t file_size_bytes);
+
+// Logs statistics about the result of a model operation. Used to track failure
+// cases.
+void LogModelOperationResult(ModelAction action, bool success);
+
+// Logs the total number of all entries, and the number of entries in each
+// state after the model is initialized.
+void LogEntries(std::map<Entry::State, uint32_t>& entries_count);
+
+// Log statistics about the status of a TaskFinishedCallback.
+void LogScheduledTaskStatus(DownloadTaskType task_type,
+ ScheduledTaskStatus status);
+
+// Logs download files directory creation error.
+void LogsFileDirectoryCreationError(base::File::Error error);
+
+// Logs statistics about the reasons of a file cleanup.
+void LogFileCleanupStatus(FileCleanupReason reason,
+ int succeeded_cleanups,
+ int failed_cleanups,
+ int external_cleanups);
+
+// Logs the file life time for successfully completed download.
+void LogFileLifeTime(const base::TimeDelta& file_life_time);
+
+// Logs the total disk space utilized by download files.
+// This includes the total size of all the files in |file_dir|.
+// This function is costly and should be called only once.
+void LogFileDirDiskUtilization(int64_t total_disk_space,
+ int64_t free_disk_space,
+ int64_t files_size);
+
+// Logs if the final download file path is different from the requested file
+// path.
+void LogFilePathRenamed(bool renamed);
+
+} // namespace stats
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_INTERNAL_STATS_H_
diff --git a/chromium/components/download/internal/store.h b/chromium/components/download/internal/store.h
index 04a39bffcc6..1d1e5686687 100644
--- a/chromium/components/download/internal/store.h
+++ b/chromium/components/download/internal/store.h
@@ -10,8 +10,6 @@
#include <vector>
#include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "base/sequenced_task_runner.h"
namespace download {
@@ -36,9 +34,8 @@ class Store {
// Store.
virtual void Initialize(InitCallback callback) = 0;
- // Destroyes the store and asynchronously returns whether or not that
- // destruction was successful.
- virtual void Destroy(StoreCallback callback) = 0;
+ // Destroys the underlying store and attempts to re-initialize.
+ virtual void HardRecover(StoreCallback callback) = 0;
// Adds or updates |entry| in this Store asynchronously and returns whether or
// not that was successful.
diff --git a/chromium/components/download/internal/test/BUILD.gn b/chromium/components/download/internal/test/BUILD.gn
index 87032dcfd7d..99011eef401 100644
--- a/chromium/components/download/internal/test/BUILD.gn
+++ b/chromium/components/download/internal/test/BUILD.gn
@@ -8,10 +8,22 @@ source_set("test_support") {
testonly = true
sources = [
+ "download_params_utils.cc",
+ "download_params_utils.h",
+ "empty_client.cc",
+ "empty_client.h",
"entry_utils.cc",
"entry_utils.h",
+ "mock_client.cc",
+ "mock_client.h",
+ "mock_controller.cc",
+ "mock_controller.h",
"mock_model_client.cc",
"mock_model_client.h",
+ "noop_store.cc",
+ "noop_store.h",
+ "test_device_status_listener.cc",
+ "test_device_status_listener.h",
"test_download_driver.cc",
"test_download_driver.h",
"test_store.cc",
diff --git a/chromium/components/download/public/BUILD.gn b/chromium/components/download/public/BUILD.gn
index 5f013fd35b6..f51a715b16d 100644
--- a/chromium/components/download/public/BUILD.gn
+++ b/chromium/components/download/public/BUILD.gn
@@ -14,8 +14,11 @@ source_set("public") {
"download_params.cc",
"download_params.h",
"download_service.h",
+ "download_task_types.h",
"features.cc",
"features.h",
+ "service_config.h",
+ "task_scheduler.h",
]
deps = [
@@ -28,3 +31,22 @@ source_set("public") {
"//url",
]
}
+
+if (is_android) {
+ android_library("public_java") {
+ srcjar_deps = [ ":jni_enums" ]
+
+ deps = [
+ "//base:base_java",
+ "//third_party/android_tools:android_support_annotations_java",
+ ]
+ }
+
+ java_cpp_enum("jni_enums") {
+ visibility = [ "*" ]
+
+ sources = [
+ "download_task_types.h",
+ ]
+ }
+}
diff --git a/chromium/components/download/public/client.h b/chromium/components/download/public/client.h
index 28264e7b97c..518d7e223b3 100644
--- a/chromium/components/download/public/client.h
+++ b/chromium/components/download/public/client.h
@@ -22,18 +22,56 @@ class Client {
// Used by OnDownloadStarted to determine whether or not the DownloadService
// should continue downloading the file or abort the attempt.
enum class ShouldDownload {
+ // Continue to download the file.
CONTINUE,
+
+ // Abort the download.
ABORT,
+
+ // The count of entries for the enum.
+ COUNT,
+ };
+
+ // Used by OnDownloadFailed to determine the reason of the abort.
+ enum class FailureReason {
+ // Used when the download has been aborted after reaching a threshold where
+ // we decide it is not worth attempting to start again. This could be
+ // either due to a specific number of failed retry attempts or a specific
+ // number of wasted bytes due to the download restarting.
+ NETWORK,
+
+ // Used when the download was not completed before the
+ // DownloadParams::cancel_after timeout.
+ TIMEDOUT,
+
+ // Used when the download was cancelled by the Client.
+ CANCELLED,
+
+ // Used when the download was aborted by the Client in response to the
+ // download starting (see OnDownloadStarted()).
+ ABORTED,
+
+ // Used when the failure reason is unknown. This generally means that we
+ // detect that the download failed during a restart, but aren't sure exactly
+ // what triggered the failure before shutdown.
+ UNKNOWN,
};
virtual ~Client() = default;
// Called when the DownloadService is initialized and ready to be interacted
// with. |outstanding_download_guids| is a list of all downloads the
- // DownloadService is aware of that are associated with this Client.
+ // DownloadService is aware of that are associated with this Client. If
+ // |state_lost| is |true|, the service ran into an error initializing and had
+ // to destroy all internal persisted state. At this point any saved files
+ // might not be available and any previously scheduled downloads are gone.
virtual void OnServiceInitialized(
+ bool state_lost,
const std::vector<std::string>& outstanding_download_guids) = 0;
+ // Called when the DownloadService fails to initialize and should not be used.
+ virtual void OnServiceUnavailable() = 0;
+
// Return whether or not the download should be aborted (potentially in
// response to |headers|). The download will be downloading at the time this
// call is made.
@@ -51,18 +89,11 @@ class Client {
virtual void OnDownloadUpdated(const std::string& guid,
uint64_t bytes_downloaded) = 0;
- // TODO(dtrainor): Expose a useful error message with the failed download.
- virtual void OnDownloadFailed(const std::string& guid) = 0;
-
- // Called when the download was not completed before the
- // DownloadParams::cancel_after timeout.
- virtual void OnDownloadTimedOut(const std::string& guid) = 0;
-
- // Called when the download has been aborted after reaching a treshold where
- // we decide it is not worth attempting to start again. This could be either
- // due to a specific number of failed retry attempts or a specific number of
- // wasted bytes due to the download restarting.
- virtual void OnDownloadAborted(const std::string& guid) = 0;
+ // Called when a download failed. Check FailureReason for a list of possible
+ // reasons why this failure occurred. Note that this will also be called for
+ // cancelled downloads.
+ virtual void OnDownloadFailed(const std::string& guid,
+ FailureReason reason) = 0;
// Called when a download has been successfully completed. After this call
// the download entry will be purged from the database. The file will be
diff --git a/chromium/components/download/public/clients.h b/chromium/components/download/public/clients.h
index a464d212017..3939aea2638 100644
--- a/chromium/components/download/public/clients.h
+++ b/chromium/components/download/public/clients.h
@@ -5,6 +5,11 @@
#ifndef COMPONENTS_DOWNLOAD_PUBLIC_CLIENTS_H_
#define COMPONENTS_DOWNLOAD_PUBLIC_CLIENTS_H_
+#include <map>
+#include <memory>
+
+#include "components/download/public/client.h"
+
namespace download {
// A list of all clients that are able to make download requests through the
@@ -15,19 +20,23 @@ namespace download {
// but also to make sure the underlying database properly associates each
// download with the right client.
enum class DownloadClient {
- // Represents an uninitialized DownloadClient variable.
- INVALID = 0,
-
// Test client values. Meant to be used by the testing framework and not
// production code. Callers will be unable to access the DownloadService with
// these test APIs.
- TEST = 1,
+ TEST = -1,
+ TEST_2 = -2,
+ TEST_3 = -3,
- OFFLINE_PAGE_PREFETCH = 2,
+ // Represents an uninitialized DownloadClient variable.
+ INVALID = 0,
- BOUNDARY = 3,
+ OFFLINE_PAGE_PREFETCH = 1,
+
+ BOUNDARY = 2,
};
+using DownloadClientMap = std::map<DownloadClient, std::unique_ptr<Client>>;
+
} // namespace download
#endif // COMPONENTS_DOWNLOAD_PUBLIC_CLIENTS_H_
diff --git a/chromium/components/download/public/download_params.cc b/chromium/components/download/public/download_params.cc
index f24d467b763..2c3f10652a2 100644
--- a/chromium/components/download/public/download_params.cc
+++ b/chromium/components/download/public/download_params.cc
@@ -9,10 +9,17 @@
namespace download {
SchedulingParams::SchedulingParams()
- : priority(Priority::DEFAULT),
+ : cancel_time(base::Time::Max()),
+ priority(Priority::DEFAULT),
network_requirements(NetworkRequirements::NONE),
battery_requirements(BatteryRequirements::BATTERY_INSENSITIVE) {}
+bool SchedulingParams::operator==(const SchedulingParams& rhs) const {
+ return network_requirements == rhs.network_requirements &&
+ battery_requirements == rhs.battery_requirements &&
+ priority == rhs.priority && cancel_time == rhs.cancel_time;
+}
+
RequestParams::RequestParams() : method("GET") {}
DownloadParams::DownloadParams() : client(DownloadClient::INVALID) {}
diff --git a/chromium/components/download/public/download_params.h b/chromium/components/download/public/download_params.h
index dcf25b41986..217718efbc9 100644
--- a/chromium/components/download/public/download_params.h
+++ b/chromium/components/download/public/download_params.h
@@ -30,6 +30,9 @@ struct SchedulingParams {
// The download can occur only if the network isn't metered.
UNMETERED = 2,
+
+ // Last value of the enum.
+ COUNT = 3,
};
enum class BatteryRequirements {
@@ -41,6 +44,9 @@ struct SchedulingParams {
// The download can only occur when charging or in optimal battery
// conditions.
BATTERY_SENSITIVE = 1,
+
+ // Last value of the enum.
+ COUNT = 2,
};
enum class Priority {
@@ -61,13 +67,19 @@ struct SchedulingParams {
// The default priority for all tasks unless overridden.
DEFAULT = NORMAL,
+
+ // Last value of the enum.
+ COUNT = 4,
};
SchedulingParams();
SchedulingParams(const SchedulingParams& other) = default;
~SchedulingParams() = default;
+ bool operator==(const SchedulingParams& rhs) const;
+
// Cancel the download after this time. Will cancel in-progress downloads.
+ // base::Time::Max() if not specified.
base::Time cancel_time;
// The suggested priority. Non-UI priorities may not be honored by the
@@ -102,12 +114,27 @@ struct DownloadParams {
// The DownloadService has too many downloads. Backoff and retry.
BACKOFF,
- // Failed to create the download. Invalid input parameters.
- BAD_PARAMETERS,
+ // The DownloadService has no knowledge of the DownloadClient associated
+ // with this request.
+ UNEXPECTED_CLIENT,
+
+ // Failed to create the download. The guid is already in use.
+ UNEXPECTED_GUID,
+
+ // The download was cancelled by the Client while it was being persisted.
+ CLIENT_CANCELLED,
+
+ // The DownloadService was unable to accept and persist this download due to
+ // an internal error like the underlying DB store failing to write to disk.
+ INTERNAL_ERROR,
// TODO(dtrainor): Add more error codes.
+ // The count of entries for the enum.
+ COUNT,
};
+ using StartCallback = base::Callback<void(const std::string&, StartResult)>;
+
DownloadParams();
DownloadParams(const DownloadParams& other);
~DownloadParams();
@@ -116,11 +143,13 @@ struct DownloadParams {
DownloadClient client;
// A unique GUID that represents this download. See |base::GenerateGUID()|.
+ // TODO(xingliu): guid in content download must be upper case, see
+ // http://crbug.com/734818.
std::string guid;
// A callback that will be notified if this download has been accepted and
// persisted by the DownloadService.
- base::Callback<void(const DownloadParams&, StartResult)> callback;
+ StartCallback callback;
// The parameters that determine under what device conditions this download
// will occur.
diff --git a/chromium/components/download/public/download_service.h b/chromium/components/download/public/download_service.h
index f0733de6ff4..fdf8ac512bf 100644
--- a/chromium/components/download/public/download_service.h
+++ b/chromium/components/download/public/download_service.h
@@ -5,6 +5,7 @@
#ifndef COMPONENTS_DOWNLOAD_PUBLIC_DOWNLOAD_SERVICE_H_
#define COMPONENTS_DOWNLOAD_PUBLIC_DOWNLOAD_SERVICE_H_
+#include <memory>
#include <string>
#include "base/files/file_path.h"
@@ -12,12 +13,18 @@
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
+#include "components/download/public/clients.h"
+#include "components/download/public/download_task_types.h"
#include "components/keyed_service/core/keyed_service.h"
namespace download {
+class Client;
struct DownloadParams;
struct SchedulingParams;
+class ServiceConfig;
+
+using TaskFinishedCallback = base::Callback<void(bool)>;
// A service responsible for helping facilitate the scheduling and downloading
// of file content from the web. See |DownloadParams| for more details on the
@@ -27,21 +34,50 @@ struct SchedulingParams;
// feature requesting a download will have to implement a download::Client
// interface so this class knows who to contact when a download completes after
// a process restart.
+// See the embedder specific factories for creation options.
class DownloadService : public KeyedService {
public:
- // |storage_dir| is a path to where all the local storage will be. This will
- // hold the internal database as well as any temporary files on disk. If this
- // is an empty path, the service will not persist any information to disk and
- // will act as an in-memory only service (this means no auto-retries after
- // restarts, no files written on completion, etc.).
- // |background_task_runner| will be used for all disk reads and writes.
- static DownloadService* Create(
- const base::FilePath& storage_dir,
- const scoped_refptr<base::SequencedTaskRunner>& background_task_runner);
+ // The current status of the Service.
+ enum class ServiceStatus {
+ // The service is in the process of initializing and should not be used yet.
+ // All registered Clients will be notified via
+ // Client::OnServiceInitialized() once the service is ready.
+ STARTING_UP = 0,
+
+ // The service is ready and available for use.
+ READY = 1,
+
+ // The service is unavailable. This is typically due to an unrecoverable
+ // error on some internal component like the persistence layer.
+ UNAVAILABLE = 2,
+ };
+
+ // Returns useful configuration information about the DownloadService.
+ virtual const ServiceConfig& GetConfig() = 0;
+
+ // Callback method to run by the service when a pre-scheduled task starts.
+ // This method is invoked on main thread and while it is running, the system
+ // holds a wakelock which is not released until either the |callback| is run
+ // or OnStopScheduledTask is invoked by the system. Do not call this method
+ // directly.
+ virtual void OnStartScheduledTask(DownloadTaskType task_type,
+ const TaskFinishedCallback& callback) = 0;
+
+ // Callback method to run by the service if the system decides to stop the
+ // task. Returns true if the task needs to be rescheduled. Any pending
+ // TaskFinishedCallback should be reset after this call. Do not call this
+ // method directly.
+ virtual bool OnStopScheduledTask(DownloadTaskType task_type) = 0;
+
+ // Whether or not the DownloadService is currently available, initialized
+ // successfully, and ready to be used.
+ virtual ServiceStatus GetStatus() = 0;
// Sends the download to the service. A callback to
// |DownloadParams::callback| will be triggered once the download has been
- // persisted and saved in the service
+ // persisted and saved in the service.
+ // TODO(xingliu): Remove the limitation of upper case guid in
+ // |download_params|, see http://crbug.com/734818.
virtual void StartDownload(const DownloadParams& download_params) = 0;
// Allows any feature to pause or resume downloads at will. Paused downloads
diff --git a/chromium/components/download/public/download_task_types.h b/chromium/components/download/public/download_task_types.h
new file mode 100644
index 00000000000..a26bc013743
--- /dev/null
+++ b/chromium/components/download/public/download_task_types.h
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_PUBLIC_DOWNLOAD_TASK_TYPES_H_
+#define COMPONENTS_DOWNLOAD_PUBLIC_DOWNLOAD_TASK_TYPES_H_
+
+namespace download {
+
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.download
+enum class DownloadTaskType {
+ // Task to invoke download service to take various download actions.
+ DOWNLOAD_TASK = 0,
+
+ // Task to remove unnecessary files from the system.
+ CLEANUP_TASK = 1,
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_PUBLIC_DOWNLOAD_TASK_TYPES_H_
diff --git a/chromium/components/download/public/service_config.h b/chromium/components/download/public/service_config.h
new file mode 100644
index 00000000000..de51fbd5dd9
--- /dev/null
+++ b/chromium/components/download/public/service_config.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_PUBLIC_SERVICE_CONFIG_H_
+#define COMPONENTS_DOWNLOAD_PUBLIC_SERVICE_CONFIG_H_
+
+#include <stdint.h>
+
+namespace base {
+class TimeDelta;
+} // namespace base
+
+namespace download {
+
+// Contains the configuration used by this DownloadService for internal download
+// operations. Meant to be used by Clients for any tweaking they might want to
+// do based on the configuration parameters.
+class ServiceConfig {
+ public:
+ virtual ~ServiceConfig() = default;
+
+ // The maximum number of downloads that can be outstanding per Client. Any
+ // Client attempting to schedule more downloads than this limit will receive a
+ // DownloadParams::StartResult::BACKOFF return value.
+ virtual uint32_t GetMaxScheduledDownloadsPerClient() const = 0;
+
+ // Returns the minimum amount of time the DownloadService will wait before
+ // automatically erasing any files that remain on disk in the same place with
+ // the same name for a completed download. It is up to the Client to move or
+ // consume the file before this time limit is reached.
+ virtual const base::TimeDelta& GetFileKeepAliveTime() const = 0;
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_PUBLIC_SERVICE_CONFIG_H_
diff --git a/chromium/components/download/public/task_scheduler.h b/chromium/components/download/public/task_scheduler.h
new file mode 100644
index 00000000000..ed436432b99
--- /dev/null
+++ b/chromium/components/download/public/task_scheduler.h
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOWNLOAD_PUBLIC_TASK_SCHEDULER_H_
+#define COMPONENTS_DOWNLOAD_PUBLIC_TASK_SCHEDULER_H_
+
+#include "components/download/public/download_task_types.h"
+
+namespace download {
+
+// A helper class backed by system APIs to schedule jobs in the background. The
+// tasks can run independently of each other as long as they have different
+// |task_type|. Scheduling another task of same |task_type| before the task is
+// fired will cancel the previous task.
+class TaskScheduler {
+ public:
+ // Schedules a task with the operating system. The system has the liberty of
+ // firing the task any time between |window_start_time_seconds| and
+ // |window_end_time_seconds|. If the trigger conditions are not met, the
+ // behavior is unknown.
+ virtual void ScheduleTask(DownloadTaskType task_type,
+ bool require_unmetered_network,
+ bool require_charging,
+ long window_start_time_seconds,
+ long window_end_time_seconds) = 0;
+
+ // Cancels a pre-scheduled task of type |task_type|.
+ virtual void CancelTask(DownloadTaskType task_type) = 0;
+
+ virtual ~TaskScheduler() {}
+};
+
+} // namespace download
+
+#endif // COMPONENTS_DOWNLOAD_PUBLIC_TASK_SCHEDULER_H_