Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(598)

Side by Side Diff: chrome/browser/safe_browsing/incident_reporting/download_metadata_manager.cc

Issue 663023007: Include high-fidelity metadata about a download in incident reports. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git/+/master
Patch Set: sync to r301101 Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/safe_browsing/incident_reporting/download_metadata_mana ger.h"
6
7 #include <list>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file.h"
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/threading/sequenced_worker_pool.h"
18 #include "chrome/common/safe_browsing/csd.pb.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/download_item.h"
21
22 namespace safe_browsing {
23
24 namespace {
25
26 // Histogram bucket values for metadata read operations. Do not reorder.
27 enum MetadataReadResult {
28 READ_SUCCESS = 0,
29 OPEN_FAILURE = 1,
30 NOT_FOUND = 2,
31 GET_INFO_FAILURE = 3,
32 FILE_TOO_BIG = 4,
33 READ_FAILURE = 5,
34 PARSE_FAILURE = 6,
35 MALFORMED_DATA = 7,
36 NUM_READ_RESULTS
37 };
38
39 // Histogram bucket values for metadata write operations. Do not reorder.
40 enum MetadataWriteResult {
41 WRITE_SUCCESS = 0,
42 SERIALIZATION_FAILURE = 1,
43 WRITE_FAILURE = 2,
44 NUM_WRITE_RESULTS
45 };
46
47 // The name of the metadata file in the profile directory.
48 const base::FilePath::CharType kDownloadMetadataBasename[] =
49 FILE_PATH_LITERAL("DownloadMetadata");
50
51 // Returns the path to the metadata file for |browser_context|.
52 base::FilePath GetMetadataPath(content::BrowserContext* browser_context) {
53 return browser_context->GetPath().Append(kDownloadMetadataBasename);
54 }
55
56 // Returns true if |metadata| appears to be valid.
57 bool MetadataIsValid(const DownloadMetadata& metadata) {
58 return (metadata.has_download_id() &&
59 metadata.has_download() &&
60 metadata.download().has_download() &&
61 metadata.download().download().has_url());
62 }
63
64 // Reads and parses a DownloadMetadata message from |metadata_path| into
65 // |metadata|.
66 void ReadMetadataOnWorkerPool(const base::FilePath& metadata_path,
67 DownloadMetadata* metadata) {
68 using base::File;
69 DCHECK(metadata);
70 MetadataReadResult result = NUM_READ_RESULTS;
71 File metadata_file(metadata_path, File::FLAG_OPEN | File::FLAG_READ);
72 if (metadata_file.IsValid()) {
73 base::File::Info info;
74 if (metadata_file.GetInfo(&info)) {
75 if (info.size <= INT_MAX) {
76 const int size = static_cast<int>(info.size);
77 scoped_ptr<char[]> file_data(new char[info.size]);
78 if (metadata_file.Read(0, file_data.get(), size)) {
79 if (!metadata->ParseFromArray(file_data.get(), size))
80 result = PARSE_FAILURE;
81 else if (!MetadataIsValid(*metadata))
82 result = MALFORMED_DATA;
83 else
84 result = READ_SUCCESS;
85 } else {
86 result = READ_FAILURE;
87 }
88 } else {
89 result = FILE_TOO_BIG;
90 }
91 } else {
92 result = GET_INFO_FAILURE;
93 }
94 } else if (metadata_file.error_details() != File::FILE_ERROR_NOT_FOUND) {
95 result = OPEN_FAILURE;
96 } else {
97 result = NOT_FOUND;
98 }
99 if (result != READ_SUCCESS)
100 metadata->Clear();
101 UMA_HISTOGRAM_ENUMERATION(
102 "SBIRS.DownloadMetadataReadResult", result, NUM_READ_RESULTS);
103 }
104
105 // Writes |download_metadata| to |metadata_path|.
106 void WriteMetadataOnWorkerPool(const base::FilePath& metadata_path,
107 DownloadMetadata* download_metadata) {
108 MetadataWriteResult result = NUM_WRITE_RESULTS;
109 std::string file_data;
110 if (download_metadata->SerializeToString(&file_data)) {
111 result =
112 base::ImportantFileWriter::WriteFileAtomically(metadata_path, file_data)
113 ? WRITE_SUCCESS
114 : WRITE_FAILURE;
115 } else {
116 result = SERIALIZATION_FAILURE;
117 }
118 UMA_HISTOGRAM_ENUMERATION(
119 "SBIRS.DownloadMetadataWriteResult", result, NUM_WRITE_RESULTS);
120 }
121
122 // Deletes |metadata_path|.
123 void DeleteMetadataOnWorkerPool(const base::FilePath& metadata_path) {
124 bool success = base::DeleteFile(metadata_path, false /* not recursive */);
125 UMA_HISTOGRAM_BOOLEAN("SBIRS.DownloadMetadataDeleteSuccess", success);
126 }
127
128 // Runs |callback| with the DownloadDetails in |download_metadata|.
129 void ReturnResults(
130 const DownloadMetadataManager::GetDownloadDetailsCallback& callback,
131 scoped_ptr<DownloadMetadata> download_metadata) {
132 if (!download_metadata->has_download_id())
133 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
134 else
135 callback.Run(make_scoped_ptr(download_metadata->release_download()).Pass());
136 }
137
138 } // namespace
139
140 // Applies operations to the profile's persistent DownloadMetadata as they occur
141 // on its corresponding download item. An instance can be in one of three
142 // states: waiting for metatada load, waiting for metadata to load after its
143 // corresponding DownloadManager has gone down, and not waiting for metadata to
144 // load. While it is waiting for metadata to load, it observes all download
145 // items and records operations on them. Once the metadata is ready, recorded
146 // operations are applied and observers are removed from all items but the one
147 // corresponding to the metadata (if it exists). The instance continues to
148 // observe its related item, and applies operations on it accordingly. While
149 // waiting for metadata to load, an instance also tracks callbacks to be run to
150 // provide cosnumers with persisted details of a download.
asanka 2014/10/28 20:55:20 nit: consumers
grt (UTC plus 2) 2014/10/30 15:11:40 Done.
151 class DownloadMetadataManager::ManagerContext
152 : public content::DownloadItem::Observer {
153 public:
154 ManagerContext(const scoped_refptr<base::SequencedTaskRunner>& read_runner,
155 const scoped_refptr<base::SequencedTaskRunner>& write_runner,
156 content::DownloadManager* download_manager);
157
158 // Detaches this context from its owner. The owner must not access the context
159 // following this call. The context will be deleted immediately if it is not
160 // waiting for a metadata load with either recorded operations or pending
161 // callbacks.
162 void Detach();
163
164 // Notifies the context that |download| has been added to its manager.
165 void OnDownloadCreated(content::DownloadItem* download);
166
167 // Sets |request| as the relevant metadata to persist for |download|.
168 void SetRequest(content::DownloadItem* download,
169 const ClientDownloadRequest& request);
170
171 // Removes metadata for the context from memory and posts a task in the worker
172 // pool to delete it on disk.
173 void RemoveMetadata();
174
175 // Gets the persisted DownloadDetails. |callback| will be run immediately if
176 // the data is available. Otherwise, it will be run later on the caller's
177 // thread.
178 void GetDownloadDetails(const GetDownloadDetailsCallback& callback);
179
180 protected:
181 // content::DownloadItem::Observer methods.
182 void OnDownloadOpened(content::DownloadItem* download) override;
183 void OnDownloadRemoved(content::DownloadItem* download) override;
184 void OnDownloadDestroyed(content::DownloadItem* download) override;
185
186 private:
187 enum State {
188 // The context is waiting for the metadata file to be loaded.
189 WAITING_FOR_LOAD,
190
191 // The context is waiting for the metadata file to be loaded and its
192 // corresponding DownloadManager has gone away.
193 DETACHED_WAIT,
194
195 // The context has loaded the metadata file.
196 LOAD_COMPLETE,
197 };
198
199 struct ItemData {
200 ItemData() : item(), removed() {}
201 // null if the download has been destroyed. If non-null, the item is being
202 // observed.
203 content::DownloadItem* item;
204 base::Time last_opened_time;
205 bool removed;
206 };
207
208 // A mapping of download IDs to their corresponding data.
209 typedef std::map<uint32_t, ItemData> ItemDataMap;
210
211 virtual ~ManagerContext();
212
213 // Clears the |pending_items_| mapping, removing observers in the process.
214 void ClearPendingItems();
215
216 // Runs all |get_details_callbacks_| with the current metadata.
217 void RunCallbacks();
218
219 // Returns the id of the download corresponding to the loaded metadata, or
220 // kInvalidId if metadata has not finished loading or is not present.
221 uint32_t GetDownloadId() const;
222
223 // Posts a task in the worker pool to read the metadata from disk.
224 void ReadMetadata();
225
226 // A callback run on the main thread with the results from reading the
227 // metadata file from disk.
228 void OnMetadataReady(scoped_ptr<DownloadMetadata> download_metadata);
229
230 // Updates the last opened time in the metadata and writes it to disk.
231 void UpdateLastOpenedTime(const base::Time& last_opened_time);
232
233 // Posts a task in the worker pool to write the metadata to disk.
234 void WriteMetadata();
235
236 // A task runner to which read tasks are posted.
237 scoped_refptr<base::SequencedTaskRunner> read_runner_;
238
239 // A task runner to which write tasks are posted.
240 scoped_refptr<base::SequencedTaskRunner> write_runner_;
241
242 // The path to the metadata file for this context.
243 base::FilePath metadata_path_;
244
245 // When true, the context is waiting for a pending read operation to complete.
asanka 2014/10/28 20:55:20 Nit: this appears to document a boolean.
grt (UTC plus 2) 2014/10/30 15:11:40 Done.
246 // While this is the case, all added download items are observed and events
247 // are temporarily recorded in |pending_items_|. Once the read completes,
248 // pending operations for the item correponding to the metadata file are
249 // applied to the file and all other recorded data are dropped (and
250 // corresponding observers are removed). Queued GetDownloadDetailsCallbacks
251 // are run upon read completion as well.
252 State state_;
253
254 // The current metadata for the context. May be supplied either by reading
255 // from the file or by having been set via |SetRequest|.
256 scoped_ptr<DownloadMetadata> download_metadata_;
257
258 // The operation data that accumulates for added download items while the
259 // metadata file is being read.
260 ItemDataMap pending_items_;
261
262 // The download item corresponding to the download_metadata_. When non-null,
263 // the context observes events on this item only.
264 content::DownloadItem* item_;
265
266 // Pending callbacks in response to GetDownloadDetails. The callbacks are run
267 // in order when a pending read operation completes.
268 std::list<GetDownloadDetailsCallback> get_details_callbacks_;
269
270 base::WeakPtrFactory<ManagerContext> weak_factory_;
271
272 DISALLOW_COPY_AND_ASSIGN(ManagerContext);
273 };
274
275 DownloadMetadataManager::DownloadMetadataManager(
276 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) {
277 base::SequencedWorkerPool::SequenceToken token(
278 worker_pool->GetSequenceToken());
279 // Do not block shutdown on reads since nothing will come of it.
280 read_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
281 token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
282 // Block shutdown on writes only if they've already begun.
283 write_runner_ = worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
284 token, base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
285 }
286
287 DownloadMetadataManager::DownloadMetadataManager(
288 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
289 : read_runner_(task_runner),
290 write_runner_(task_runner) {
291 }
292
293 DownloadMetadataManager::~DownloadMetadataManager() {
294 // All download managers must have gone down prior to this.
295 DCHECK(contexts_.empty());
296 }
297
298 void DownloadMetadataManager::AddDownloadManager(
299 content::DownloadManager* download_manager) {
300 DCHECK_EQ(contexts_.count(download_manager), 0U);
301 download_manager->AddObserver(this);
302 contexts_[download_manager] =
303 new ManagerContext(read_runner_, write_runner_, download_manager);
304 }
305
306 void DownloadMetadataManager::SetRequest(content::DownloadItem* item,
307 const ClientDownloadRequest* request) {
308 content::DownloadManager* download_manager =
309 GetDownloadManagerForBrowserContext(item->GetBrowserContext());
310 DCHECK_EQ(contexts_.count(download_manager), 1U);
311 if (request)
312 contexts_[download_manager]->SetRequest(item, *request);
313 else
314 contexts_[download_manager]->RemoveMetadata();
315 }
316
317 void DownloadMetadataManager::GetDownloadDetails(
318 content::BrowserContext* browser_context,
319 const GetDownloadDetailsCallback& callback) {
320 DCHECK(browser_context);
321 // The DownloadManager for |browser_context| may not have been created yet. In
322 // this case, asking for it would cause history to load in the background and
323 // wouldn't really help much. Instead, scan the contexts to see if one belongs
324 // to |browser_context|. If one is not found, read the metadata and return it.
325 scoped_ptr<ClientIncidentReport_DownloadDetails> download_details;
326 for (const auto& value : contexts_) {
327 if (value.first->GetBrowserContext() == browser_context) {
328 value.second->GetDownloadDetails(callback);
329 return;
330 }
331 }
332
333 // Fire off a task to load the details and return them to the caller.
334 DownloadMetadata* metadata = new DownloadMetadata();
335 read_runner_->PostTaskAndReply(
336 FROM_HERE,
337 base::Bind(&ReadMetadataOnWorkerPool,
338 GetMetadataPath(browser_context),
339 metadata),
340 base::Bind(
341 &ReturnResults, callback, base::Passed(make_scoped_ptr(metadata))));
342 }
343
344 content::DownloadManager*
345 DownloadMetadataManager::GetDownloadManagerForBrowserContext(
346 content::BrowserContext* context) {
347 return content::BrowserContext::GetDownloadManager(context);
348 }
349
350 void DownloadMetadataManager::OnDownloadCreated(
351 content::DownloadManager* download_manager,
352 content::DownloadItem* item) {
353 DCHECK_EQ(contexts_.count(download_manager), 1U);
354 contexts_[download_manager]->OnDownloadCreated(item);
355 }
356
357 void DownloadMetadataManager::ManagerGoingDown(
358 content::DownloadManager* download_manager) {
359 DCHECK_EQ(contexts_.count(download_manager), 1U);
360 auto iter = contexts_.find(download_manager);
361 iter->second->Detach();
362 contexts_.erase(iter);
363 }
364
365 DownloadMetadataManager::ManagerContext::ManagerContext(
366 const scoped_refptr<base::SequencedTaskRunner>& read_runner,
367 const scoped_refptr<base::SequencedTaskRunner>& write_runner,
368 content::DownloadManager* download_manager)
369 : read_runner_(read_runner),
370 write_runner_(write_runner),
371 metadata_path_(GetMetadataPath(download_manager->GetBrowserContext())),
372 state_(WAITING_FOR_LOAD),
373 item_(),
374 weak_factory_(this) {
375 // Start the asynchronous task to read the persistent metadata.
376 ReadMetadata();
377 }
378
379 void DownloadMetadataManager::ManagerContext::Detach() {
380 // Delete the instance immediately if there's no work to process after a
381 // pending read completes.
382 if (state_ == LOAD_COMPLETE ||
383 (get_details_callbacks_.empty() && pending_items_.empty())) {
asanka 2014/10/28 20:55:20 nit: state == LOAD_COMPLETE => (no callbacks && no
grt (UTC plus 2) 2014/10/30 15:11:40 Done.
384 delete this;
385 } else {
386 // delete the instance in OnMetadataReady.
387 state_ = DETACHED_WAIT;
388 }
389 }
390
391 void DownloadMetadataManager::ManagerContext::OnDownloadCreated(
392 content::DownloadItem* download) {
393 const uint32_t id = download->GetId();
394 if (state_ != LOAD_COMPLETE) {
395 // Observe all downloads and record operations while waiting for metadata to
396 // load.
397 download->AddObserver(this);
398 pending_items_[id].item = download;
399 } else if (id == GetDownloadId()) {
400 // Observe this one item if it is the important one.
401 DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr));
402 item_ = download;
403 download->AddObserver(this);
404 }
405 }
406
407 void DownloadMetadataManager::ManagerContext::SetRequest(
408 content::DownloadItem* download,
409 const ClientDownloadRequest& request) {
410 if (state_ != LOAD_COMPLETE) {
411 // Abandon the read task since |download| is the new top dog.
412 weak_factory_.InvalidateWeakPtrs();
413 state_ = LOAD_COMPLETE;
414 // Stop observing all items and drop any recorded operations.
415 ClearPendingItems();
416 }
417 // Observe this item from here on out.
418 if (item_ != download) {
419 if (item_)
420 item_->RemoveObserver(this);
421 download->AddObserver(this);
422 item_ = download;
423 }
424 // Take the request.
425 download_metadata_.reset(new DownloadMetadata);
426 download_metadata_->set_download_id(download->GetId());
427 download_metadata_->mutable_download()->mutable_download()->CopyFrom(request);
428 base::Time end_time = download->GetEndTime();
429 // The item may have yet to be marked as complete, so consider the current
430 // time as being its download time.
431 if (end_time.is_null())
432 end_time = base::Time::Now();
433 download_metadata_->mutable_download()->set_download_time_msec(
434 end_time.ToJavaTime());
435 // Persist it.
436 WriteMetadata();
437 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
438 RunCallbacks();
439 }
440
441 void DownloadMetadataManager::ManagerContext::RemoveMetadata() {
442 if (state_ != LOAD_COMPLETE) {
443 // Abandon the read task since the file is to be removed.
444 weak_factory_.InvalidateWeakPtrs();
445 state_ = LOAD_COMPLETE;
446 // Stop observing all items and drop any recorded operations.
447 ClearPendingItems();
448 }
449 // Remove any metadata.
450 download_metadata_.reset();
451 write_runner_->PostTask(
452 FROM_HERE, base::Bind(&DeleteMetadataOnWorkerPool, metadata_path_));
453 // Run callbacks (only present in case of a transition to LOAD_COMPLETE).
454 RunCallbacks();
455 }
456
457 void DownloadMetadataManager::ManagerContext::GetDownloadDetails(
458 const GetDownloadDetailsCallback& callback) {
459 if (state_ != LOAD_COMPLETE) {
460 get_details_callbacks_.push_back(callback);
461 } else {
462 if (download_metadata_) {
463 callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
464 download_metadata_->download())).Pass());
465 } else {
466 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
467 }
468 }
469 }
470
471 void DownloadMetadataManager::ManagerContext::OnDownloadOpened(
472 content::DownloadItem* download) {
473 const base::Time now = base::Time::Now();
474 if (state_ != LOAD_COMPLETE)
475 pending_items_[download->GetId()].last_opened_time = now;
476 else
477 UpdateLastOpenedTime(now);
478 }
479
480 void DownloadMetadataManager::ManagerContext::OnDownloadRemoved(
481 content::DownloadItem* download) {
482 if (state_ != LOAD_COMPLETE)
483 pending_items_[download->GetId()].removed = true;
484 else
485 RemoveMetadata();
486 }
487
488 void DownloadMetadataManager::ManagerContext::OnDownloadDestroyed(
489 content::DownloadItem* download) {
490 if (state_ != LOAD_COMPLETE) {
491 // Erase the data for this item if nothing of import happened. Otherwise
492 // clear its item pointer since it is now invalid.
493 auto iter = pending_items_.find(download->GetId());
494 DCHECK(iter != pending_items_.end());
495 if (!iter->second.removed && iter->second.last_opened_time.is_null())
496 pending_items_.erase(iter);
497 else
498 iter->second.item = nullptr;
499 } else if (item_) {
500 // This item is no longer being observed.
501 DCHECK_EQ(item_, download);
502 item_ = nullptr;
503 }
504 }
505
506 DownloadMetadataManager::ManagerContext::~ManagerContext() {
507 // All downloads must have been destroyed prior to deleting the context and
508 // all recorded operations and callbacks must have been handled.
509 DCHECK(pending_items_.empty());
510 DCHECK_EQ(item_, static_cast<content::DownloadItem*>(nullptr));
511 DCHECK(get_details_callbacks_.empty());
512 }
513
514 void DownloadMetadataManager::ManagerContext::ClearPendingItems() {
515 for (const auto& value : pending_items_) {
516 if (value.second.item)
517 value.second.item->RemoveObserver(this);
518 }
519 pending_items_.clear();
520 }
521
522 void DownloadMetadataManager::ManagerContext::RunCallbacks() {
523 while (!get_details_callbacks_.empty()) {
524 const auto& callback = get_details_callbacks_.front();
525 if (download_metadata_) {
526 callback.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
527 download_metadata_->download())).Pass());
528 } else {
529 callback.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
530 }
531 get_details_callbacks_.pop_front();
532 }
533 }
534
535 uint32_t DownloadMetadataManager::ManagerContext::GetDownloadId() const {
536 if (state_ != LOAD_COMPLETE || !download_metadata_)
537 return content::DownloadItem::kInvalidId;
538 return download_metadata_->download_id();
539 }
540
541 void DownloadMetadataManager::ManagerContext::ReadMetadata() {
542 DCHECK_NE(state_, LOAD_COMPLETE);
543
544 DownloadMetadata* metadata = new DownloadMetadata();
545 // Do not block shutdown on this read since nothing will come of it.
546 read_runner_->PostTaskAndReply(
547 FROM_HERE,
548 base::Bind(&ReadMetadataOnWorkerPool, metadata_path_, metadata),
549 base::Bind(&DownloadMetadataManager::ManagerContext::OnMetadataReady,
550 weak_factory_.GetWeakPtr(),
551 base::Passed(make_scoped_ptr(metadata))));
552 }
553
554 void DownloadMetadataManager::ManagerContext::OnMetadataReady(
555 scoped_ptr<DownloadMetadata> download_metadata) {
556 DCHECK_NE(state_, LOAD_COMPLETE);
557
558 const bool is_detached = (state_ == DETACHED_WAIT);
559
560 // Note that any available data has been read.
561 state_ = LOAD_COMPLETE;
562 if (download_metadata->has_download_id())
563 download_metadata_ = download_metadata.Pass();
564 else
565 download_metadata_.reset();
566
567 // Process all operations that had been held while waiting for the metadata.
568 content::DownloadItem* download = nullptr;
569 {
570 const auto& iter = pending_items_.find(GetDownloadId());
571 if (iter != pending_items_.end()) {
572 const ItemData& item_data = iter->second;
573 download = item_data.item; // non-null if not destroyed.
574 if (item_data.removed)
575 RemoveMetadata();
576 else if (!item_data.last_opened_time.is_null())
577 UpdateLastOpenedTime(item_data.last_opened_time);
578 }
579 }
580
581 // Stop observing all items.
582 ClearPendingItems();
583
584 // If the download was known, observe it from here on out.
585 if (download) {
586 download->AddObserver(this);
587 item_ = download;
588 }
589
590 // Run callbacks.
591 RunCallbacks();
592
593 // Delete the context now if it has been detached.
594 if (is_detached)
595 delete this;
596 }
597
598 void DownloadMetadataManager::ManagerContext::UpdateLastOpenedTime(
599 const base::Time& last_opened_time) {
600 download_metadata_->mutable_download()->set_open_time_msec(
601 last_opened_time.ToJavaTime());
602 WriteMetadata();
603 }
604
605 void DownloadMetadataManager::ManagerContext::WriteMetadata() {
606 write_runner_->PostTask(
607 FROM_HERE,
608 base::Bind(&WriteMetadataOnWorkerPool,
609 metadata_path_,
610 base::Owned(new DownloadMetadata(*download_metadata_))));
611 }
612
613 } // namespace safe_browsing
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698