Chromium Code Reviews| Index: chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc |
| diff --git a/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc b/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..04bce7c75a22e3b768367dca16113cb448ac5b06 |
| --- /dev/null |
| +++ b/chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc |
| @@ -0,0 +1,610 @@ |
| +// Copyright 2014 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 "chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.h" |
| + |
| +#include <list> |
| + |
| +#include "base/bind.h" |
| +#include "base/callback.h" |
| +#include "base/files/file.h" |
| +#include "base/files/file_util.h" |
| +#include "base/files/important_file_writer.h" |
| +#include "base/location.h" |
| +#include "base/metrics/histogram.h" |
| +#include "base/sequenced_task_runner.h" |
| +#include "base/threading/sequenced_worker_pool.h" |
| +#include "chrome/common/safe_browsing/csd.pb.h" |
| +#include "content/public/browser/browser_context.h" |
| +#include "content/public/browser/download_item.h" |
| + |
| +namespace safe_browsing { |
| + |
| +namespace { |
| + |
| +// Histogram bucket values for metadata read operations. Do not reorder. |
| +enum MetadataReadResult { |
| + READ_SUCCESS = 0, |
| + OPEN_FAILURE = 1, |
| + NOT_FOUND = 2, |
| + GET_INFO_FAILURE = 3, |
| + FILE_TOO_BIG = 4, |
| + READ_FAILURE = 5, |
| + PARSE_FAILURE = 6, |
| + MALFORMED_DATA = 7, |
| + NUM_READ_RESULTS |
| +}; |
| + |
| +// Histogram bucket values for metadata write operations. Do not reorder. |
| +enum MetadataWriteResult { |
| + WRITE_SUCCESS = 0, |
| + SERIALIZATION_FAILURE = 1, |
| + WRITE_FAILURE = 2, |
| + NUM_WRITE_RESULTS |
| +}; |
| + |
| +// The name of the metadata file in the profile directory. |
| +const base::FilePath::CharType kDownloadMetadataBasename[] = |
| + FILE_PATH_LITERAL("DownloadMetadata"); |
| + |
| +// Returns the path to the metadata file for |browser_context|. |
| +base::FilePath GetMetadataPath(content::BrowserContext* browser_context) { |
| + return browser_context->GetPath().Append(kDownloadMetadataBasename); |
| +} |
| + |
| +// Returns true if |metadata| appears to be a valid |
|
robertshield
2014/10/21 13:15:38
truncated sentence?
grt (UTC plus 2)
2014/10/23 19:53:13
I'm not sure what your point
|
| +bool MetadataIsValid(const DownloadMetadata& metadata) { |
| + return (metadata.has_download_id() && |
| + metadata.has_download() && |
| + metadata.download().has_download() && |
| + metadata.download().download().has_url()); |
| +} |
| + |
| +// Reads and parses a DownloadMetadata message from |metadata_path| into |
| +// |metadata|. |
| +void ReadMetadataOnWorkerPool(const base::FilePath& metadata_path, |
| + DownloadMetadata* metadata) { |
| + using base::File; |
|
robertshield
2014/10/22 02:28:56
DCHECK(metadata) ?
grt (UTC plus 2)
2014/10/23 19:53:13
Done.
|
| + MetadataReadResult result = NUM_READ_RESULTS; |
| + File metadata_file(metadata_path, File::FLAG_OPEN | File::FLAG_READ); |
| + if (metadata_file.IsValid()) { |
| + base::File::Info info; |
| + if (metadata_file.GetInfo(&info)) { |
| + if (info.size <= INT_MAX) { |
| + const int size = static_cast<int>(info.size); |
| + scoped_ptr<char[]> file_data(new char[info.size]); |
| + if (metadata_file.Read(0, file_data.get(), size)) { |
| + if (!metadata->ParseFromArray(file_data.get(), size)) |
| + result = PARSE_FAILURE; |
| + else if (!MetadataIsValid(*metadata)) |
| + result = MALFORMED_DATA; |
| + else |
| + result = READ_SUCCESS; |
| + } else { |
| + result = READ_FAILURE; |
| + } |
| + } else { |
| + result = FILE_TOO_BIG; |
| + } |
| + } else { |
| + result = GET_INFO_FAILURE; |
| + } |
| + } else if (metadata_file.error_details() != File::FILE_ERROR_NOT_FOUND) { |
| + result = OPEN_FAILURE; |
| + } else { |
| + result = NOT_FOUND; |
| + } |
| + if (result != READ_SUCCESS) |
| + metadata->Clear(); |
| + UMA_HISTOGRAM_ENUMERATION( |
| + "SBIRS.DownloadMetadataReadResult", result, NUM_READ_RESULTS); |
| +} |
| + |
| +// Writes |download_metadata| to |metadata_path|. |
| +void WriteMetadataOnWorkerPool(const base::FilePath& metadata_path, |
| + DownloadMetadata* download_metadata) { |
| + MetadataWriteResult result = NUM_WRITE_RESULTS; |
| + std::string file_data; |
| + if (download_metadata->SerializeToString(&file_data)) { |
| + result = |
| + base::ImportantFileWriter::WriteFileAtomically(metadata_path, file_data) |
| + ? WRITE_SUCCESS |
| + : WRITE_FAILURE; |
| + } else { |
| + result = SERIALIZATION_FAILURE; |
| + } |
| + UMA_HISTOGRAM_ENUMERATION( |
| + "SBIRS.DownloadMetadataWriteResult", result, NUM_WRITE_RESULTS); |
| +} |
| + |
| +// Deletes |metadata_path|. |
| +void DeleteMetadataOnWorkerPool(const base::FilePath& metadata_path) { |
| + bool success = base::DeleteFile(metadata_path, false /* not recursive */); |
| + UMA_HISTOGRAM_BOOLEAN("SBIRS.DownloadMetadataDeleteSuccess", success); |
| +} |
| + |
| +// Runs |callback| with the DownloadDetails in |download_metadata|. |
| +void ReturnResults( |
| + const DownloadMetadataManager::GetDownloadDetailsCallback& callback, |
| + scoped_ptr<DownloadMetadata> download_metadata) { |
| + if (!download_metadata->has_download_id()) |
| + callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); |
| + else |
| + callback.Run(make_scoped_ptr(download_metadata->release_download()).Pass()); |
| +} |
| + |
| +} // namespace |
| + |
| +// Applies operations to the profile's persistent DownloadMetadata as they occur |
| +// on its corresponding download item. An instance can be in one of three |
| +// states: waiting for metatada load, waiting for metadata to load after its |
| +// corresponding DownloadManager has gone down, and not waiting for metadata to |
| +// load. While it is waiting for metadata to load, it observes all download |
| +// items and records operations on them. Once the metadata is ready, recorded |
| +// operations are applied and observers are removed from all items but the one |
| +// corresponding to the metadata (if it exists). The instance continues to |
| +// observe its related item, and applies operations on it accordingly. While |
| +// waiting for metadata to load, an instance also tracks callbacks to be run to |
| +// provide cosnumers with persisted details of a download. |
| +class DownloadMetadataManager::ManagerContext |
| + : public content::DownloadItem::Observer { |
| + public: |
| + ManagerContext(const scoped_refptr<base::SequencedTaskRunner>& read_runner, |
| + const scoped_refptr<base::SequencedTaskRunner>& write_runner, |
| + content::DownloadManager* download_manager); |
| + |
| + // Detaches this context from its owner. The owner must not access the context |
| + // following this call. The context will be deleted immediately if it is not |
| + // waiting for a metadata load with either recorded operations or pending |
| + // callbacks. |
| + void Detach(); |
| + |
| + // Notifies the context that |download| has been added to its manager. |
| + void OnDownloadCreated(content::DownloadItem* download); |
| + |
| + // Sets |request| as the relevant metadata to persist for |download|. |
| + void SetRequest(content::DownloadItem* download, |
| + const ClientDownloadRequest& request); |
| + |
| + // Removes metadata for the context from memory and posts a task in the worker |
| + // pool to delete it on disk. |
| + void RemoveMetadata(); |
| + |
| + // Gets the persisted DownloadDetails. |callback| will be run immediately if |
| + // the data is available. Otherwise, it will be run later on the caller's |
| + // thread. |
| + void GetDownloadDetails(const GetDownloadDetailsCallback& callback); |
| + |
| + protected: |
| + // content::DownloadItem::Observer methods. |
| + void OnDownloadOpened(content::DownloadItem* download) override; |
| + void OnDownloadRemoved(content::DownloadItem* download) override; |
| + void OnDownloadDestroyed(content::DownloadItem* download) override; |
| + |
| + private: |
| + enum State { |
| + // The context is waiting for the metadata file to be loaded. |
| + WAITING_FOR_LOAD, |
| + |
| + // The context is waiting for the metadata file to be loaded and its |
| + // corresponding DownloadManager has gone away. |
| + DETACHED_WAIT, |
| + |
| + // The context has loaded the metadata file. |
| + LOAD_COMPLETE, |
| + }; |
| + |
| + struct ItemData { |
| + ItemData() : item(), removed() {} |
| + // null if the download has been destroyed. If non-null, the item is being |
|
robertshield
2014/10/22 02:28:56
item(NULL), removed(false)? (I find that clearer)
grt (UTC plus 2)
2014/10/23 19:53:13
get off my lawn.
robertshield
2014/10/23 20:30:27
If I do will you make your code more readable?
grt (UTC plus 2)
2014/10/23 21:02:18
Value-initialization is a fundamental part of the
robertshield
2014/10/23 21:43:03
My readability comment was really based on the not
grt (UTC plus 2)
2014/10/24 01:51:30
What does that buy you? As you point out, members
robertshield
2014/10/24 03:05:46
Thank you for considering it.
|
| + // observed. |
| + content::DownloadItem* item; |
| + base::Time last_opened_time; |
| + bool removed; |
| + }; |
| + |
| + // A mapping of download IDs to their corresponding data. |
| + typedef std::map<uint32_t, ItemData> ItemDataMap; |
| + |
| + virtual ~ManagerContext(); |
| + |
| + // Clears the |pending_items_| mapping, removing observers in the process. |
| + void ClearPendingItems(); |
| + |
| + // Runs all |get_details_callbacks_| with the current metadata. |
| + void RunCallbacks(); |
| + |
| + // Returns the id of the download corresponding to the loaded metadata, or |
| + // kInvalidId if metadata has not finished loading or is not present. |
| + uint32_t GetDownloadId() const; |
| + |
| + // Posts a task in the worker pool to read the metadata from disk. |
| + void ReadMetadata(); |
| + |
| + // A callback run on the main thread with the results from reading the |
| + // metadata file from disk. |
| + void OnMetadataReady(scoped_ptr<DownloadMetadata> download_metadata); |
| + |
| + // Updates the last opened time in the metadata and writes it to disk. |
| + void UpdateLastOpenedTime(const base::Time& last_opened_time); |
| + |
| + // Posts a task in the worker pool to write the metadata to disk. |
| + void WriteMetadata(); |
| + |
| + // A task runner to which read tasks are posted. |
| + scoped_refptr<base::SequencedTaskRunner> read_runner_; |
| + |
| + // A task runner to which write tasks are posted. |
| + scoped_refptr<base::SequencedTaskRunner> write_runner_; |
| + |
| + // The path to the metadata file for this context. |
| + base::FilePath metadata_path_; |
| + |
| + // When true, the context is waiting for a pending read operation to complete. |
| + // While this is the case, all added download items are observed and events |
| + // are temporarily recorded in |pending_items_|. Once the read completes, |
| + // pending operations for the item correponding to the metadata file are |
| + // applied to the file and all other recorded data are dropped (and |
| + // corresponding observers are removed). Queued GetDownloadDetailsCallbacks |
| + // are run upon read completion as well. |
| + State state_; |
| + |
| + // The current metadata for the context. May be supplied either by reading |
| + // from the file or by having been set via |SetRequest|. |
| + scoped_ptr<DownloadMetadata> download_metadata_; |
| + |
| + // The operation data that accumulates for added download items while the |
| + // metadata file is being read. |
| + ItemDataMap pending_items_; |
| + |
| + // The download item corresponding to the download_metadata_. When non-null, |
| + // the context observes events on this item only. |
| + content::DownloadItem* item_; |
| + |
| + // Pending callbacks in response to GetDownloadDetails. The callbacks are run |
| + // in order when a pending read operation completes. |
| + std::list<GetDownloadDetailsCallback> get_details_callbacks_; |
| + |
| + base::WeakPtrFactory<ManagerContext> weak_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ManagerContext); |
| +}; |
| + |
| +DownloadMetadataManager::DownloadMetadataManager( |
| + const scoped_refptr<base::SequencedWorkerPool>& worker_pool) { |
| + base::SequencedWorkerPool::SequenceToken token( |
| + worker_pool->GetSequenceToken()); |
| + // Do not block shutdown on reads since nothing will come of it. |
|
robertshield
2014/10/21 13:15:38
double space
grt (UTC plus 2)
2014/10/23 19:53:13
Done.
|
| + read_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| + token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); |
| + // Block shutdown on writes in the hopes of persisting important data. |
|
robertshield
2014/10/21 13:15:38
Which data being persisted is important enough to
grt (UTC plus 2)
2014/10/23 19:53:13
The effect is just that we don't have the data. Ar
robertshield
2014/10/23 20:30:27
A little. I am basing this off the comment here: h
grt (UTC plus 2)
2014/10/23 21:02:18
Yeah, I saw that comment. I'd been thinking of it
robertshield
2014/10/23 21:43:03
I think so, yes. My thinking is that this data won
grt (UTC plus 2)
2014/10/24 01:51:30
SGTM, done.
|
| + write_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| + token, base::SequencedWorkerPool::BLOCK_SHUTDOWN); |
| +} |
| + |
| +DownloadMetadataManager::DownloadMetadataManager( |
| + const scoped_refptr<base::SequencedTaskRunner>& task_runner) |
| + : read_runner_(task_runner), |
| + write_runner_(task_runner) { |
| +} |
| + |
| +DownloadMetadataManager::~DownloadMetadataManager() { |
| + // All download managers must have gone down prior to this. |
| + DCHECK(contexts_.empty()); |
| +} |
| + |
| +void DownloadMetadataManager::AddDownloadManager( |
| + content::DownloadManager* download_manager) { |
| + DCHECK_EQ(contexts_.count(download_manager), 0U); |
| + download_manager->AddObserver(this); |
| + contexts_[download_manager] = |
| + new ManagerContext(read_runner_, write_runner_, download_manager); |
| +} |
| + |
| +void DownloadMetadataManager::SetRequest(content::DownloadItem* item, |
| + const ClientDownloadRequest* request) { |
| + content::DownloadManager* download_manager = |
| + GetDownloadManagerForContext(item->GetBrowserContext()); |
| + DCHECK_EQ(contexts_.count(download_manager), 1U); |
| + if (request) |
| + contexts_[download_manager]->SetRequest(item, *request); |
| + else |
| + contexts_[download_manager]->RemoveMetadata(); |
| +} |
| + |
| +void DownloadMetadataManager::GetDownloadDetails( |
| + content::BrowserContext* browser_context, |
| + const GetDownloadDetailsCallback& callback) { |
|
robertshield
2014/10/22 02:28:56
This is part of the public interface, so should yo
grt (UTC plus 2)
2014/10/23 19:53:13
Done.
|
| + // The DownloadManager for |browser_context| may not have been created yet. In |
| + // this case, asking for it would cause history to load in the background and |
| + // wouldn't really help much. Instead, scan the contexts to see if one belongs |
| + // to |browser_context|. If one is not found, read the metadata and return it. |
| + scoped_ptr<ClientIncidentReport_DownloadDetails> download_details; |
| + for (const auto& value : contexts_) { |
| + if (value.first->GetBrowserContext() == browser_context) { |
| + value.second->GetDownloadDetails(callback); |
| + return; |
| + } |
| + } |
| + |
| + // Fire off a task to load the details and return them to the caller. |
| + DownloadMetadata* metadata = new DownloadMetadata(); |
| + read_runner_->PostTaskAndReply( |
| + FROM_HERE, |
| + base::Bind(&ReadMetadataOnWorkerPool, |
| + GetMetadataPath(browser_context), |
| + metadata), |
| + base::Bind( |
| + &ReturnResults, callback, base::Passed(make_scoped_ptr(metadata)))); |
| +} |
| + |
| +content::DownloadManager* DownloadMetadataManager::GetDownloadManagerForContext( |
| + content::BrowserContext* context) { |
| + return content::BrowserContext::GetDownloadManager(context); |
| +} |
| + |
| +void DownloadMetadataManager::OnDownloadCreated( |
| + content::DownloadManager* download_manager, |
| + content::DownloadItem* item) { |
| + DCHECK_EQ(contexts_.count(download_manager), 1U); |
| + contexts_[download_manager]->OnDownloadCreated(item); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerGoingDown( |
| + content::DownloadManager* download_manager) { |
| + DCHECK_EQ(contexts_.count(download_manager), 1U); |
| + auto iter = contexts_.find(download_manager); |
| + iter->second->Detach(); |
| + contexts_.erase(iter); |
| +} |
| + |
| +DownloadMetadataManager::ManagerContext::ManagerContext( |
| + const scoped_refptr<base::SequencedTaskRunner>& read_runner, |
| + const scoped_refptr<base::SequencedTaskRunner>& write_runner, |
| + content::DownloadManager* download_manager) |
| + : read_runner_(read_runner), |
| + write_runner_(write_runner), |
| + metadata_path_(GetMetadataPath(download_manager->GetBrowserContext())), |
| + state_(WAITING_FOR_LOAD), |
| + item_(), |
| + weak_factory_(this) { |
| + // Start the asynchronous task to read the persistent metadata. |
| + ReadMetadata(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::Detach() { |
| + // Delete the instance immediately if there's no work to process after a |
| + // pending read completes. |
| + if (state_ == LOAD_COMPLETE || |
| + (get_details_callbacks_.empty() && pending_items_.empty())) { |
| + delete this; |
| + } else { |
| + // delete the instance in OnMetadataReady. |
| + state_ = DETACHED_WAIT; |
| + } |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::OnDownloadCreated( |
| + content::DownloadItem* download) { |
| + const uint32_t id = download->GetId(); |
| + if (state_ != LOAD_COMPLETE) { |
| + // Observe all downloads and record operations while waiting for metadata to |
| + // load. |
| + download->AddObserver(this); |
| + pending_items_[id].item = download; |
| + } else if (id == GetDownloadId()) { |
| + // Observe this one item if it is the important one. |
| + DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr)); |
| + item_ = download; |
| + download->AddObserver(this); |
| + } |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::SetRequest( |
| + content::DownloadItem* download, |
| + const ClientDownloadRequest& request) { |
| + if (state_ != LOAD_COMPLETE) { |
| + // Abandon the read task since |download| is the new top dog. |
| + weak_factory_.InvalidateWeakPtrs(); |
| + state_ = LOAD_COMPLETE; |
| + // Stop observing all items and drop any recorded operations. |
| + ClearPendingItems(); |
| + } |
| + // Observe this item from here on out. |
| + if (item_ != download) { |
| + if (item_) |
| + item_->RemoveObserver(this); |
| + download->AddObserver(this); |
| + item_ = download; |
| + } |
| + // Take the request. |
| + download_metadata_.reset(new DownloadMetadata); |
| + download_metadata_->set_download_id(download->GetId()); |
| + download_metadata_->mutable_download()->mutable_download()->CopyFrom(request); |
| + base::Time end_time = download->GetEndTime(); |
| + // The item may have yet to be marked as complete, so consider the current |
| + // time as being its download time. |
| + if (end_time.is_null()) |
| + end_time = base::Time::Now(); |
| + download_metadata_->mutable_download()->set_download_time_msec( |
| + end_time.ToJavaTime()); |
| + // Persist it. |
| + WriteMetadata(); |
| + // Run callbacks (only present in case of a transition to LOAD_COMPLETE). |
| + RunCallbacks(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::RemoveMetadata() { |
| + if (state_ != LOAD_COMPLETE) { |
| + // Abandon the read task since the file is to be removed. |
| + weak_factory_.InvalidateWeakPtrs(); |
| + state_ = LOAD_COMPLETE; |
| + // Stop observing all items and drop any recorded operations. |
| + ClearPendingItems(); |
| + } |
| + // Remove any metadata. |
| + download_metadata_.reset(); |
| + write_runner_->PostTask( |
| + FROM_HERE, base::Bind(&DeleteMetadataOnWorkerPool, metadata_path_)); |
| + // Run callbacks (only present in case of a transition to LOAD_COMPLETE). |
| + RunCallbacks(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::GetDownloadDetails( |
| + const GetDownloadDetailsCallback& callback) { |
| + if (state_ != LOAD_COMPLETE) { |
| + get_details_callbacks_.push_back(callback); |
| + } else { |
| + if (download_metadata_) { |
| + callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails( |
| + download_metadata_->download())).Pass()); |
| + } else { |
| + callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); |
| + } |
| + } |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::OnDownloadOpened( |
| + content::DownloadItem* download) { |
| + const base::Time now = base::Time::Now(); |
| + if (state_ != LOAD_COMPLETE) |
| + pending_items_[download->GetId()].last_opened_time = now; |
| + else |
| + UpdateLastOpenedTime(now); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::OnDownloadRemoved( |
| + content::DownloadItem* download) { |
| + if (state_ != LOAD_COMPLETE) |
| + pending_items_[download->GetId()].removed = true; |
| + else |
| + RemoveMetadata(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::OnDownloadDestroyed( |
| + content::DownloadItem* download) { |
| + if (state_ != LOAD_COMPLETE) { |
| + // Erase the data for this item if nothing of import happened. Otherwise |
| + // clear its item pointer since it is now invalid. |
| + auto iter = pending_items_.find(download->GetId()); |
| + DCHECK(iter != pending_items_.end()); |
| + if (!iter->second.removed && iter->second.last_opened_time.is_null()) |
| + pending_items_.erase(iter); |
| + else |
| + iter->second.item = nullptr; |
| + } else if (item_) { |
| + // This item is no longer being observed. |
| + DCHECK_EQ(item_, download); |
| + item_ = nullptr; |
| + } |
| +} |
| + |
| +DownloadMetadataManager::ManagerContext::~ManagerContext() { |
| + // All downloads must have been destroyed prior to deleting the context and |
| + // all recorded operations and callbacks must have been handled. |
| + DCHECK(pending_items_.empty()); |
| + DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr)); |
| + DCHECK(get_details_callbacks_.empty()); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::ClearPendingItems() { |
| + for (const auto& value : pending_items_) { |
| + if (value.second.item) |
| + value.second.item->RemoveObserver(this); |
| + } |
| + pending_items_.clear(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::RunCallbacks() { |
| + while (!get_details_callbacks_.empty()) { |
| + const auto& callback = get_details_callbacks_.front(); |
| + if (download_metadata_) { |
| + callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails( |
| + download_metadata_->download())).Pass()); |
| + } else { |
| + callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); |
| + } |
| + get_details_callbacks_.pop_front(); |
| + } |
| +} |
| + |
| +uint32_t DownloadMetadataManager::ManagerContext::GetDownloadId() const { |
| + if (state_ != LOAD_COMPLETE || !download_metadata_) |
| + return content::DownloadItem::kInvalidId; |
| + return download_metadata_->download_id(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::ReadMetadata() { |
| + DCHECK_NE(state_, LOAD_COMPLETE); |
| + |
| + DownloadMetadata* metadata = new DownloadMetadata(); |
| + // Do not block shutdown on this read since nothing will come of it. |
| + read_runner_->PostTaskAndReply( |
| + FROM_HERE, |
| + base::Bind(&ReadMetadataOnWorkerPool, metadata_path_, metadata), |
| + base::Bind(&DownloadMetadataManager::ManagerContext::OnMetadataReady, |
| + weak_factory_.GetWeakPtr(), |
| + base::Passed(make_scoped_ptr(metadata)))); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::OnMetadataReady( |
| + scoped_ptr<DownloadMetadata> download_metadata) { |
| + DCHECK_NE(state_, LOAD_COMPLETE); |
| + |
| + const bool is_detached = (state_ == DETACHED_WAIT); |
| + |
| + // Note that any available data has been read. |
| + state_ = LOAD_COMPLETE; |
| + if (download_metadata->has_download_id()) |
| + download_metadata_ = download_metadata.Pass(); |
| + else |
| + download_metadata_.reset(); |
| + |
| + // Process all operations that had been held while waiting for the metadata. |
| + content::DownloadItem* download = nullptr; |
| + { |
| + const auto& iter = pending_items_.find(GetDownloadId()); |
| + if (iter != pending_items_.end()) { |
| + const ItemData& item_data = iter->second; |
| + download = item_data.item; // non-null if not destroyed. |
| + if (item_data.removed) |
| + RemoveMetadata(); |
| + else if (!item_data.last_opened_time.is_null()) |
| + UpdateLastOpenedTime(item_data.last_opened_time); |
| + } |
| + } |
| + |
| + // Stop observing all items. |
| + ClearPendingItems(); |
| + |
| + // If the download was known, observe it from here on out. |
| + if (download) { |
| + download->AddObserver(this); |
| + item_ = download; |
| + } |
| + |
| + // Run callbacks. |
| + RunCallbacks(); |
| + |
| + // Delete the context now if it has been detached. |
| + if (is_detached) |
| + delete this; |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::UpdateLastOpenedTime( |
| + const base::Time& last_opened_time) { |
| + download_metadata_->mutable_download()->set_open_time_msec( |
| + last_opened_time.ToJavaTime()); |
| + WriteMetadata(); |
| +} |
| + |
| +void DownloadMetadataManager::ManagerContext::WriteMetadata() { |
| + write_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&WriteMetadataOnWorkerPool, |
| + metadata_path_, |
| + base::Owned(new DownloadMetadata(*download_metadata_)))); |
| +} |
| + |
| +} // namespace safe_browsing |