blob: 6aa9ec0ab22e24257a8615789429fd33b5d374c7 [file] [log] [blame]
// 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/downloader/in_progress/in_progress_cache_impl.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/download/downloader/in_progress/in_progress_conversions.h"
namespace download {
const base::FilePath::CharType kDownloadMetadataStoreFilename[] =
FILE_PATH_LITERAL("in_progress_download_metadata_store");
namespace {
// Helper functions for |entries_| related operations.
int GetIndexFromEntries(const metadata_pb::DownloadEntries& entries,
const std::string& guid) {
int size_of_entries = entries.entries_size();
for (int i = 0; i < size_of_entries; i++) {
if (entries.entries(i).guid() == guid)
return i;
}
return -1;
}
void AddOrReplaceEntryInEntries(metadata_pb::DownloadEntries& entries,
const DownloadEntry& entry) {
metadata_pb::DownloadEntry metadata_entry =
InProgressConversions::DownloadEntryToProto(entry);
int entry_index = GetIndexFromEntries(entries, metadata_entry.guid());
metadata_pb::DownloadEntry* entry_ptr =
(entry_index < 0) ? entries.add_entries()
: entries.mutable_entries(entry_index);
*entry_ptr = metadata_entry;
}
base::Optional<DownloadEntry> GetEntryFromEntries(
const metadata_pb::DownloadEntries& entries,
const std::string& guid) {
int entry_index = GetIndexFromEntries(entries, guid);
if (entry_index < 0)
return base::nullopt;
return InProgressConversions::DownloadEntryFromProto(
entries.entries(entry_index));
}
void RemoveEntryFromEntries(metadata_pb::DownloadEntries& entries,
const std::string& guid) {
int entry_index = GetIndexFromEntries(entries, guid);
if (entry_index >= 0)
entries.mutable_entries()->DeleteSubrange(entry_index, 1);
}
// Helper functions for file read/write operations.
std::vector<char> ReadEntriesFromFile(base::FilePath file_path) {
// Check validity of file.
base::File entries_file(file_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!entries_file.IsValid()) {
// The file just doesn't exist yet (potentially because no entries have been
// written yet) so we can't read from it.
return std::vector<char>();
}
// Get file info.
base::File::Info info;
if (!entries_file.GetInfo(&info)) {
LOG(ERROR) << "Could not read download entries from file "
<< "because get info failed.";
return std::vector<char>();
}
if (info.is_directory) {
LOG(ERROR) << "Could not read download entries from file "
<< "because file is a directory.";
return std::vector<char>();
}
// Read and parse file.
if (info.size < 0) {
LOG(ERROR) << "Could not read download entries from file "
<< "because the file size was unexpected.";
return std::vector<char>();
}
auto file_data = std::vector<char>(info.size);
if (entries_file.Read(0, file_data.data(), info.size) < 0) {
LOG(ERROR) << "Could not read download entries from file "
<< "because there was a read failure.";
return std::vector<char>();
}
return file_data;
}
std::string EntriesToString(const metadata_pb::DownloadEntries& entries) {
std::string entries_string;
if (!entries.SerializeToString(&entries_string)) {
// TODO(crbug.com/778425): Have more robust error-handling for serialization
// error.
LOG(ERROR) << "Could not write download entries to file "
<< "because of a serialization issue.";
return std::string();
}
return entries_string;
}
void WriteEntriesToFile(const std::string& entries, base::FilePath file_path) {
DCHECK(!file_path.empty());
if (!base::ImportantFileWriter::WriteFileAtomically(file_path, entries)) {
LOG(ERROR) << "Could not write download entries to file: "
<< file_path.value();
}
}
} // namespace
InProgressCacheImpl::InProgressCacheImpl(
const base::FilePath& cache_file_path,
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: file_path_(cache_file_path),
initialization_status_(CACHE_UNINITIALIZED),
task_runner_(task_runner),
weak_ptr_factory_(this) {}
InProgressCacheImpl::~InProgressCacheImpl() = default;
void InProgressCacheImpl::Initialize(const base::RepeatingClosure& callback) {
// If it's already initialized, just run the callback.
if (initialization_status_ == CACHE_INITIALIZED) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
return;
}
pending_actions_.push_back(callback);
// If uninitialized, initialize |entries_| by reading from file.
if (initialization_status_ == CACHE_UNINITIALIZED) {
base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE,
base::BindOnce(&ReadEntriesFromFile, file_path_),
base::BindOnce(&InProgressCacheImpl::OnInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
}
void InProgressCacheImpl::OnInitialized(const std::vector<char>& entries) {
if (!entries.empty()) {
if (!entries_.ParseFromArray(entries.data(), entries.size())) {
// TODO(crbug.com/778425): Get UMA for errors.
LOG(ERROR) << "Could not read download entries from file "
<< "because there was a parse failure.";
return;
}
}
initialization_status_ = CACHE_INITIALIZED;
while (!pending_actions_.empty()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
pending_actions_.front());
pending_actions_.pop_front();
}
}
void InProgressCacheImpl::AddOrReplaceEntry(const DownloadEntry& entry) {
if (initialization_status_ != CACHE_INITIALIZED) {
LOG(ERROR) << "Cache is not initialized, cannot AddOrReplaceEntry.";
return;
}
// Update |entries_|.
AddOrReplaceEntryInEntries(entries_, entry);
// Serialize |entries_| and write to file.
std::string entries_string = EntriesToString(entries_);
task_runner_->PostTask(FROM_HERE, base::BindOnce(&WriteEntriesToFile,
entries_string, file_path_));
}
base::Optional<DownloadEntry> InProgressCacheImpl::RetrieveEntry(
const std::string& guid) {
if (initialization_status_ != CACHE_INITIALIZED) {
LOG(ERROR) << "Cache is not initialized, cannot RetrieveEntry.";
return base::nullopt;
}
return GetEntryFromEntries(entries_, guid);
}
void InProgressCacheImpl::RemoveEntry(const std::string& guid) {
if (initialization_status_ != CACHE_INITIALIZED) {
LOG(ERROR) << "Cache is not initialized, cannot RemoveEntry.";
return;
}
// Update |entries_|.
RemoveEntryFromEntries(entries_, guid);
// Serialize |entries_| and write to file.
std::string entries_string = EntriesToString(entries_);
task_runner_->PostTask(FROM_HERE, base::BindOnce(&WriteEntriesToFile,
entries_string, file_path_));
}
} // namespace download