1 // Copyright 2019 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/media/history/media_history_store.h"
6
7 #include "base/callback.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/metrics/histogram_functions.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/task_runner_util.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/media/feeds/media_feeds_service.h"
15 #include "chrome/browser/media/history/media_history_feed_items_table.h"
16 #include "chrome/browser/media/history/media_history_feeds_table.h"
17 #include "chrome/browser/media/history/media_history_images_table.h"
18 #include "chrome/browser/media/history/media_history_kaleidoscope_data_table.h"
19 #include "chrome/browser/media/history/media_history_origin_table.h"
20 #include "chrome/browser/media/history/media_history_playback_table.h"
21 #include "chrome/browser/media/history/media_history_session_images_table.h"
22 #include "chrome/browser/media/history/media_history_session_table.h"
23 #include "content/public/browser/media_player_watch_time.h"
24 #include "net/cookies/cookie_change_dispatcher.h"
25 #include "services/media_session/public/cpp/media_image.h"
26 #include "services/media_session/public/cpp/media_position.h"
27 #include "sql/recovery.h"
28 #include "sql/statement.h"
29 #include "sql/transaction.h"
30 #include "url/origin.h"
31
32 #if !defined(OS_ANDROID)
33 #include "chrome/browser/media/feeds/media_feeds_service.h"
34 #endif // !defined(OS_ANDROID)
35
36 namespace {
37
38 constexpr int kCurrentVersionNumber = 4;
39 constexpr int kCompatibleVersionNumber = 1;
40
41 constexpr base::FilePath::CharType kMediaHistoryDatabaseName[] =
42 FILE_PATH_LITERAL("Media History");
43
DatabaseErrorCallback(sql::Database * db,const base::FilePath & db_path,int extended_error,sql::Statement * stmt)44 void DatabaseErrorCallback(sql::Database* db,
45 const base::FilePath& db_path,
46 int extended_error,
47 sql::Statement* stmt) {
48 if (sql::Recovery::ShouldRecover(extended_error)) {
49 // Prevent reentrant calls.
50 db->reset_error_callback();
51
52 // After this call, the |db| handle is poisoned so that future calls will
53 // return errors until the handle is re-opened.
54 sql::Recovery::RecoverDatabase(db, db_path);
55
56 // The DLOG(FATAL) below is intended to draw immediate attention to errors
57 // in newly-written code. Database corruption is generally a result of OS
58 // or hardware issues, not coding errors at the client level, so displaying
59 // the error would probably lead to confusion. The ignored call signals the
60 // test-expectation framework that the error was handled.
61 ignore_result(sql::Database::IsExpectedSqliteError(extended_error));
62 return;
63 }
64
65 // The default handling is to assert on debug and to ignore on release.
66 if (!sql::Database::IsExpectedSqliteError(extended_error))
67 DLOG(FATAL) << db->GetErrorMessage();
68 }
69
GetDBPath(Profile * profile)70 base::FilePath GetDBPath(Profile* profile) {
71 // If this is a testing profile then we should use an in-memory database.
72 if (profile->AsTestingProfile())
73 return base::FilePath();
74 return profile->GetPath().Append(kMediaHistoryDatabaseName);
75 }
76
MigrateFrom1To2(sql::Database * db,sql::MetaTable * meta_table)77 int MigrateFrom1To2(sql::Database* db, sql::MetaTable* meta_table) {
78 // Version 2 adds a new column to mediaFeed.
79 const int target_version = 2;
80
81 // The mediaFeed table might not exist if the feature is disabled.
82 if (!db->DoesTableExist("mediaFeed")) {
83 meta_table->SetVersionNumber(target_version);
84 return target_version;
85 }
86
87 static const char k1To2Sql[] =
88 "ALTER TABLE mediaFeed ADD COLUMN cookie_name_filter TEXT;";
89 sql::Transaction transaction(db);
90 if (transaction.Begin() && db->Execute(k1To2Sql) && transaction.Commit()) {
91 meta_table->SetVersionNumber(target_version);
92 return target_version;
93 }
94 return 1;
95 }
96
MigrateFrom2To3(sql::Database * db,sql::MetaTable * meta_table)97 int MigrateFrom2To3(sql::Database* db, sql::MetaTable* meta_table) {
98 // Version 3 drops the mediaFeedAssociatedOrigin table.
99 const int target_version = 3;
100
101 // The mediaFeedAssociatedOrigin table might not exist if the feature is
102 // disabled.
103 if (!db->DoesTableExist("mediaFeedAssociatedOrigin")) {
104 meta_table->SetVersionNumber(target_version);
105 return target_version;
106 }
107
108 static const char k2To3Sql[] = "DROP TABLE mediaFeedAssociatedOrigin;";
109 sql::Transaction transaction(db);
110 if (transaction.Begin() && db->Execute(k2To3Sql) && transaction.Commit()) {
111 meta_table->SetVersionNumber(target_version);
112 return target_version;
113 }
114 return 2;
115 }
116
MigrateFrom3To4(sql::Database * db,sql::MetaTable * meta_table)117 int MigrateFrom3To4(sql::Database* db, sql::MetaTable* meta_table) {
118 // Version 4 adds a new column to mediaFeed.
119 const int target_version = 3;
120
121 // The mediaFeed table might not exist if the feature is disabled.
122 if (!db->DoesTableExist("mediaFeed")) {
123 meta_table->SetVersionNumber(target_version);
124 return target_version;
125 }
126
127 static const char k3To4Sql[] =
128 "ALTER TABLE mediaFeed ADD COLUMN safe_search_result INTEGER DEFAULT 0;";
129 sql::Transaction transaction(db);
130 if (transaction.Begin() && db->Execute(k3To4Sql) && transaction.Commit()) {
131 meta_table->SetVersionNumber(target_version);
132 return target_version;
133 }
134 return 3;
135 }
136
IsCauseFromExpiration(const net::CookieChangeCause & cause)137 bool IsCauseFromExpiration(const net::CookieChangeCause& cause) {
138 return cause == net::CookieChangeCause::UNKNOWN_DELETION ||
139 cause == net::CookieChangeCause::EXPIRED ||
140 cause == net::CookieChangeCause::EXPIRED_OVERWRITE ||
141 cause == net::CookieChangeCause::EXPLICIT ||
142 cause == net::CookieChangeCause::EVICTED;
143 }
144
IsMediaFeedsEnabled()145 bool IsMediaFeedsEnabled() {
146 #if defined(OS_ANDROID)
147 return false;
148 #else
149 return media_feeds::MediaFeedsService::IsEnabled();
150 #endif // defined(OS_ANDROID)
151 }
152
153 } // namespace
154
GetCurrentVersion()155 int GetCurrentVersion() {
156 return kCurrentVersionNumber;
157 }
158
159 namespace media_history {
160
161 const char MediaHistoryStore::kInitResultHistogramName[] =
162 "Media.History.Init.Result";
163
164 const char MediaHistoryStore::kInitResultAfterDeleteHistogramName[] =
165 "Media.History.Init.ResultAfterDelete";
166
167 const char MediaHistoryStore::kPlaybackWriteResultHistogramName[] =
168 "Media.History.Playback.WriteResult";
169
170 const char MediaHistoryStore::kSessionWriteResultHistogramName[] =
171 "Media.History.Session.WriteResult";
172
173 const char MediaHistoryStore::kDatabaseSizeKbHistogramName[] =
174 "Media.History.DatabaseSize";
175
MediaHistoryStore(Profile * profile,scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner)176 MediaHistoryStore::MediaHistoryStore(
177 Profile* profile,
178 scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner)
179 : db_task_runner_(db_task_runner),
180 db_path_(GetDBPath(profile)),
181 db_(std::make_unique<sql::Database>()),
182 meta_table_(std::make_unique<sql::MetaTable>()),
183 origin_table_(new MediaHistoryOriginTable(db_task_runner_)),
184 playback_table_(new MediaHistoryPlaybackTable(db_task_runner_)),
185 session_table_(new MediaHistorySessionTable(db_task_runner_)),
186 session_images_table_(
187 new MediaHistorySessionImagesTable(db_task_runner_)),
188 images_table_(new MediaHistoryImagesTable(db_task_runner_)),
189 feeds_table_(IsMediaFeedsEnabled()
190 ? new MediaHistoryFeedsTable(db_task_runner_)
191 : nullptr),
192 feed_items_table_(IsMediaFeedsEnabled()
193 ? new MediaHistoryFeedItemsTable(db_task_runner_)
194 : nullptr),
195 kaleidoscope_table_(
196 new MediaHistoryKaleidoscopeDataTable(db_task_runner_)),
197 initialization_successful_(false) {
198 db_->set_histogram_tag("MediaHistory");
199 db_->set_exclusive_locking();
200
201 // To recover from corruption.
202 db_->set_error_callback(
203 base::BindRepeating(&DatabaseErrorCallback, db_.get(), db_path_));
204 }
205
~MediaHistoryStore()206 MediaHistoryStore::~MediaHistoryStore() {
207 // The connection pointer needs to be deleted on the DB sequence since there
208 // might be a task in progress on the DB sequence which uses this connection.
209 if (meta_table_)
210 db_task_runner_->DeleteSoon(FROM_HERE, meta_table_.release());
211 if (db_)
212 db_task_runner_->DeleteSoon(FROM_HERE, db_.release());
213 }
214
DB()215 sql::Database* MediaHistoryStore::DB() {
216 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
217 return db_.get();
218 }
219
SavePlayback(std::unique_ptr<content::MediaPlayerWatchTime> watch_time)220 void MediaHistoryStore::SavePlayback(
221 std::unique_ptr<content::MediaPlayerWatchTime> watch_time) {
222 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
223 if (!CanAccessDatabase())
224 return;
225
226 if (!DB()->BeginTransaction()) {
227 LOG(ERROR) << "Failed to begin the transaction.";
228
229 base::UmaHistogramEnumeration(
230 MediaHistoryStore::kPlaybackWriteResultHistogramName,
231 MediaHistoryStore::PlaybackWriteResult::kFailedToEstablishTransaction);
232
233 return;
234 }
235
236 // TODO(https://crbug.com/1052436): Remove the separate origin.
237 auto origin = url::Origin::Create(watch_time->origin);
238 if (origin != url::Origin::Create(watch_time->url)) {
239 DB()->RollbackTransaction();
240
241 base::UmaHistogramEnumeration(
242 MediaHistoryStore::kPlaybackWriteResultHistogramName,
243 MediaHistoryStore::PlaybackWriteResult::kFailedToWriteBadOrigin);
244
245 return;
246 }
247
248 if (!CreateOriginId(origin)) {
249 DB()->RollbackTransaction();
250
251 base::UmaHistogramEnumeration(
252 MediaHistoryStore::kPlaybackWriteResultHistogramName,
253 MediaHistoryStore::PlaybackWriteResult::kFailedToWriteOrigin);
254
255 return;
256 }
257
258 if (!playback_table_->SavePlayback(*watch_time)) {
259 DB()->RollbackTransaction();
260
261 base::UmaHistogramEnumeration(
262 MediaHistoryStore::kPlaybackWriteResultHistogramName,
263 MediaHistoryStore::PlaybackWriteResult::kFailedToWritePlayback);
264
265 return;
266 }
267
268 if (watch_time->has_audio && watch_time->has_video) {
269 if (!origin_table_->IncrementAggregateAudioVideoWatchTime(
270 origin, watch_time->cumulative_watch_time)) {
271 DB()->RollbackTransaction();
272
273 base::UmaHistogramEnumeration(
274 MediaHistoryStore::kPlaybackWriteResultHistogramName,
275 MediaHistoryStore::PlaybackWriteResult::
276 kFailedToIncrementAggreatedWatchtime);
277
278 return;
279 }
280 }
281
282 DB()->CommitTransaction();
283
284 base::UmaHistogramEnumeration(
285 MediaHistoryStore::kPlaybackWriteResultHistogramName,
286 MediaHistoryStore::PlaybackWriteResult::kSuccess);
287 }
288
Initialize(const bool should_reset)289 void MediaHistoryStore::Initialize(const bool should_reset) {
290 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
291
292 if (should_reset) {
293 if (!sql::Database::Delete(db_path_)) {
294 LOG(ERROR) << "Failed to delete the old database.";
295
296 base::UmaHistogramEnumeration(
297 MediaHistoryStore::kInitResultHistogramName,
298 MediaHistoryStore::InitResult::kFailedToDeleteOldDatabase);
299
300 return;
301 }
302 }
303
304 if (IsCancelled())
305 return;
306
307 auto result = InitializeInternal();
308
309 if (IsCancelled()) {
310 meta_table_.reset();
311 db_.reset();
312 return;
313 }
314
315 base::UmaHistogramEnumeration(MediaHistoryStore::kInitResultHistogramName,
316 result);
317
318 // In some edge cases the DB might be corrupted and unrecoverable so we should
319 // delete the database and recreate it.
320 if (result != InitResult::kSuccess) {
321 db_ = std::make_unique<sql::Database>();
322 meta_table_ = std::make_unique<sql::MetaTable>();
323
324 sql::Database::Delete(db_path_);
325
326 base::UmaHistogramEnumeration(
327 MediaHistoryStore::kInitResultAfterDeleteHistogramName,
328 InitializeInternal());
329 }
330 }
331
InitializeInternal()332 MediaHistoryStore::InitResult MediaHistoryStore::InitializeInternal() {
333 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
334
335 if (db_path_.empty()) {
336 if (IsCancelled() || !db_ || !db_->OpenInMemory()) {
337 LOG(ERROR) << "Failed to open the in-memory database.";
338
339 return MediaHistoryStore::InitResult::kFailedToOpenDatabase;
340 }
341 } else {
342 base::File::Error err;
343 if (IsCancelled() ||
344 !base::CreateDirectoryAndGetError(db_path_.DirName(), &err)) {
345 LOG(ERROR) << "Failed to create the directory.";
346
347 return MediaHistoryStore::InitResult::kFailedToCreateDirectory;
348 }
349
350 if (IsCancelled() || !db_ || !db_->Open(db_path_)) {
351 LOG(ERROR) << "Failed to open the database.";
352
353 return MediaHistoryStore::InitResult::kFailedToOpenDatabase;
354 }
355 }
356
357 if (IsCancelled() || !db_ || !db_->Execute("PRAGMA foreign_keys=1")) {
358 LOG(ERROR) << "Failed to enable foreign keys on the media history store.";
359
360 return MediaHistoryStore::InitResult::kFailedNoForeignKeys;
361 }
362
363 if (IsCancelled() || !db_ || !meta_table_ ||
364 !meta_table_->Init(db_.get(), GetCurrentVersion(),
365 kCompatibleVersionNumber)) {
366 LOG(ERROR) << "Failed to create the meta table.";
367
368 return MediaHistoryStore::InitResult::kFailedToCreateMetaTable;
369 }
370
371 if (IsCancelled() || !db_ || !db_->BeginTransaction()) {
372 LOG(ERROR) << "Failed to begin the transaction.";
373
374 return MediaHistoryStore::InitResult::kFailedToEstablishTransaction;
375 }
376
377 sql::InitStatus status = CreateOrUpgradeIfNeeded();
378 if (status != sql::INIT_OK) {
379 LOG(ERROR) << "Failed to create or update the media history store.";
380
381 return MediaHistoryStore::InitResult::kFailedDatabaseTooNew;
382 }
383
384 status = InitializeTables();
385 if (status != sql::INIT_OK) {
386 LOG(ERROR) << "Failed to initialize the media history store tables.";
387
388 return MediaHistoryStore::InitResult::kFailedInitializeTables;
389 }
390
391 if (IsCancelled() || !db_ || !DB()->CommitTransaction()) {
392 LOG(ERROR) << "Failed to commit transaction.";
393
394 return MediaHistoryStore::InitResult::kFailedToCommitTransaction;
395 }
396
397 initialization_successful_ = true;
398
399 // Get the size in bytes.
400 int64_t file_size = 0;
401 base::GetFileSize(db_path_, &file_size);
402
403 // Record the size in KB.
404 if (file_size > 0) {
405 base::UmaHistogramMemoryKB(MediaHistoryStore::kDatabaseSizeKbHistogramName,
406 file_size / 1000);
407 }
408
409 return MediaHistoryStore::InitResult::kSuccess;
410 }
411
CreateOrUpgradeIfNeeded()412 sql::InitStatus MediaHistoryStore::CreateOrUpgradeIfNeeded() {
413 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
414 if (IsCancelled() || !meta_table_)
415 return sql::INIT_FAILURE;
416
417 int cur_version = meta_table_->GetVersionNumber();
418 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersionNumber) {
419 LOG(WARNING) << "Media history database is too new.";
420 return sql::INIT_TOO_NEW;
421 }
422
423 // Versions 0 and below are unexpected.
424 if (cur_version <= 0)
425 return sql::INIT_FAILURE;
426
427 // NOTE: Insert schema upgrade scripts here when required.
428 if (cur_version == 1)
429 cur_version = MigrateFrom1To2(db_.get(), meta_table_.get());
430 if (cur_version == 2)
431 cur_version = MigrateFrom2To3(db_.get(), meta_table_.get());
432 if (cur_version == 3)
433 cur_version = MigrateFrom3To4(db_.get(), meta_table_.get());
434
435 if (cur_version == kCurrentVersionNumber)
436 return sql::INIT_OK;
437 return sql::INIT_FAILURE;
438 }
439
InitializeTables()440 sql::InitStatus MediaHistoryStore::InitializeTables() {
441 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
442 if (IsCancelled() || !db_)
443 return sql::INIT_FAILURE;
444
445 sql::InitStatus status = origin_table_->Initialize(db_.get());
446 if (status == sql::INIT_OK)
447 status = playback_table_->Initialize(db_.get());
448 if (status == sql::INIT_OK)
449 status = session_table_->Initialize(db_.get());
450 if (status == sql::INIT_OK)
451 status = session_images_table_->Initialize(db_.get());
452 if (status == sql::INIT_OK)
453 status = images_table_->Initialize(db_.get());
454 if (feeds_table_ && status == sql::INIT_OK)
455 status = feeds_table_->Initialize(db_.get());
456 if (feed_items_table_ && status == sql::INIT_OK)
457 status = feed_items_table_->Initialize(db_.get());
458 if (status == sql::INIT_OK)
459 status = kaleidoscope_table_->Initialize(db_.get());
460
461 return status;
462 }
463
CreateOriginId(const url::Origin & origin)464 bool MediaHistoryStore::CreateOriginId(const url::Origin& origin) {
465 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
466 if (!CanAccessDatabase())
467 return false;
468
469 return origin_table_->CreateOriginId(origin);
470 }
471
GetMediaHistoryStats()472 mojom::MediaHistoryStatsPtr MediaHistoryStore::GetMediaHistoryStats() {
473 mojom::MediaHistoryStatsPtr stats(mojom::MediaHistoryStats::New());
474
475 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
476 if (!CanAccessDatabase())
477 return stats;
478
479 sql::Statement statement(DB()->GetUniqueStatement(
480 "SELECT name FROM sqlite_master WHERE type='table' "
481 "AND name NOT LIKE 'sqlite_%';"));
482
483 std::vector<std::string> table_names;
484 while (statement.Step()) {
485 auto table_name = statement.ColumnString(0);
486 stats->table_row_counts.emplace(table_name, GetTableRowCount(table_name));
487 }
488
489 DCHECK(statement.Succeeded());
490 return stats;
491 }
492
493 std::vector<mojom::MediaHistoryOriginRowPtr>
GetOriginRowsForDebug()494 MediaHistoryStore::GetOriginRowsForDebug() {
495 std::vector<mojom::MediaHistoryOriginRowPtr> origins;
496 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
497 if (!CanAccessDatabase())
498 return origins;
499
500 sql::Statement statement(DB()->GetUniqueStatement(
501 base::StringPrintf(
502 "SELECT O.origin, O.last_updated_time_s, "
503 "O.aggregate_watchtime_audio_video_s, "
504 "(SELECT SUM(watch_time_s) FROM %s WHERE origin_id = O.id AND "
505 "has_video = 1 AND has_audio = 1) AS accurate_watchtime "
506 "FROM %s O",
507 MediaHistoryPlaybackTable::kTableName,
508 MediaHistoryOriginTable::kTableName)
509 .c_str()));
510
511 std::vector<std::string> table_names;
512 while (statement.Step()) {
513 mojom::MediaHistoryOriginRowPtr origin(mojom::MediaHistoryOriginRow::New());
514
515 origin->origin = url::Origin::Create(GURL(statement.ColumnString(0)));
516 origin->last_updated_time =
517 base::Time::FromDeltaSinceWindowsEpoch(
518 base::TimeDelta::FromSeconds(statement.ColumnInt64(1)))
519 .ToJsTime();
520 origin->cached_audio_video_watchtime =
521 base::TimeDelta::FromSeconds(statement.ColumnInt64(2));
522 origin->actual_audio_video_watchtime =
523 base::TimeDelta::FromSeconds(statement.ColumnInt64(3));
524
525 origins.push_back(std::move(origin));
526 }
527
528 DCHECK(statement.Succeeded());
529 return origins;
530 }
531
GetHighWatchTimeOrigins(const base::TimeDelta & audio_video_watchtime_min)532 std::vector<url::Origin> MediaHistoryStore::GetHighWatchTimeOrigins(
533 const base::TimeDelta& audio_video_watchtime_min) {
534 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
535 return origin_table_->GetHighWatchTimeOrigins(audio_video_watchtime_min);
536 }
537
538 std::vector<mojom::MediaHistoryPlaybackRowPtr>
GetMediaHistoryPlaybackRowsForDebug()539 MediaHistoryStore::GetMediaHistoryPlaybackRowsForDebug() {
540 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
541 if (!CanAccessDatabase())
542 return std::vector<mojom::MediaHistoryPlaybackRowPtr>();
543
544 return playback_table_->GetPlaybackRows();
545 }
546
547 std::vector<media_feeds::mojom::MediaFeedItemPtr>
GetMediaFeedItems(const MediaHistoryKeyedService::GetMediaFeedItemsRequest & request)548 MediaHistoryStore::GetMediaFeedItems(
549 const MediaHistoryKeyedService::GetMediaFeedItemsRequest& request) {
550 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
551 if (!CanAccessDatabase() || !feed_items_table_)
552 return std::vector<media_feeds::mojom::MediaFeedItemPtr>();
553
554 return feed_items_table_->GetItems(request);
555 }
556
GetMediaFeeds(const MediaHistoryKeyedService::GetMediaFeedsRequest & request)557 std::vector<media_feeds::mojom::MediaFeedPtr> MediaHistoryStore::GetMediaFeeds(
558 const MediaHistoryKeyedService::GetMediaFeedsRequest& request) {
559 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
560 if (!CanAccessDatabase() || !feeds_table_)
561 return std::vector<media_feeds::mojom::MediaFeedPtr>();
562
563 return feeds_table_->GetRows(request);
564 }
565
GetTableRowCount(const std::string & table_name)566 int MediaHistoryStore::GetTableRowCount(const std::string& table_name) {
567 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
568 if (!CanAccessDatabase())
569 return -1;
570
571 sql::Statement statement(DB()->GetUniqueStatement(
572 base::StringPrintf("SELECT count(*) from %s", table_name.c_str())
573 .c_str()));
574
575 while (statement.Step()) {
576 return statement.ColumnInt(0);
577 }
578
579 return -1;
580 }
581
SavePlaybackSession(const GURL & url,const media_session::MediaMetadata & metadata,const base::Optional<media_session::MediaPosition> & position,const std::vector<media_session::MediaImage> & artwork)582 void MediaHistoryStore::SavePlaybackSession(
583 const GURL& url,
584 const media_session::MediaMetadata& metadata,
585 const base::Optional<media_session::MediaPosition>& position,
586 const std::vector<media_session::MediaImage>& artwork) {
587 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
588 if (!CanAccessDatabase())
589 return;
590
591 if (!DB()->BeginTransaction()) {
592 LOG(ERROR) << "Failed to begin the transaction.";
593
594 base::UmaHistogramEnumeration(
595 MediaHistoryStore::kSessionWriteResultHistogramName,
596 MediaHistoryStore::SessionWriteResult::kFailedToEstablishTransaction);
597
598 return;
599 }
600
601 auto origin = url::Origin::Create(url);
602 if (!CreateOriginId(origin)) {
603 DB()->RollbackTransaction();
604
605 base::UmaHistogramEnumeration(
606 MediaHistoryStore::kSessionWriteResultHistogramName,
607 MediaHistoryStore::SessionWriteResult::kFailedToWriteOrigin);
608 return;
609 }
610
611 auto session_id =
612 session_table_->SavePlaybackSession(url, origin, metadata, position);
613 if (!session_id) {
614 DB()->RollbackTransaction();
615
616 base::UmaHistogramEnumeration(
617 MediaHistoryStore::kSessionWriteResultHistogramName,
618 MediaHistoryStore::SessionWriteResult::kFailedToWriteSession);
619 return;
620 }
621
622 for (auto& image : artwork) {
623 auto image_id =
624 images_table_->SaveOrGetImage(image.src, origin, image.type);
625 if (!image_id) {
626 DB()->RollbackTransaction();
627
628 base::UmaHistogramEnumeration(
629 MediaHistoryStore::kSessionWriteResultHistogramName,
630 MediaHistoryStore::SessionWriteResult::kFailedToWriteImage);
631 return;
632 }
633
634 // If we do not have any sizes associated with the image we should save a
635 // link with a null size. Otherwise, we should save a link for each size.
636 if (image.sizes.empty()) {
637 session_images_table_->LinkImage(*session_id, *image_id, base::nullopt);
638 } else {
639 for (auto& size : image.sizes) {
640 session_images_table_->LinkImage(*session_id, *image_id, size);
641 }
642 }
643 }
644
645 DB()->CommitTransaction();
646
647 base::UmaHistogramEnumeration(
648 MediaHistoryStore::kSessionWriteResultHistogramName,
649 MediaHistoryStore::SessionWriteResult::kSuccess);
650 }
651
652 std::vector<mojom::MediaHistoryPlaybackSessionRowPtr>
GetPlaybackSessions(base::Optional<unsigned int> num_sessions,base::Optional<MediaHistoryStore::GetPlaybackSessionsFilter> filter)653 MediaHistoryStore::GetPlaybackSessions(
654 base::Optional<unsigned int> num_sessions,
655 base::Optional<MediaHistoryStore::GetPlaybackSessionsFilter> filter) {
656 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
657
658 if (!CanAccessDatabase())
659 return std::vector<mojom::MediaHistoryPlaybackSessionRowPtr>();
660
661 auto sessions =
662 session_table_->GetPlaybackSessions(num_sessions, std::move(filter));
663
664 for (auto& session : sessions) {
665 session->artwork = session_images_table_->GetImagesForSession(session->id);
666 }
667
668 return sessions;
669 }
670
DeleteAllOriginData(const std::set<url::Origin> & origins)671 void MediaHistoryStore::DeleteAllOriginData(
672 const std::set<url::Origin>& origins) {
673 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
674 if (!CanAccessDatabase())
675 return;
676
677 if (!DB()->BeginTransaction()) {
678 LOG(ERROR) << "Failed to begin the transaction.";
679 return;
680 }
681
682 for (auto& origin : origins) {
683 if (!origin_table_->Delete(origin)) {
684 DB()->RollbackTransaction();
685 return;
686 }
687 }
688
689 DB()->CommitTransaction();
690 }
691
DeleteAllURLData(const std::set<GURL> & urls)692 void MediaHistoryStore::DeleteAllURLData(const std::set<GURL>& urls) {
693 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
694 if (!CanAccessDatabase())
695 return;
696
697 if (!DB()->BeginTransaction()) {
698 LOG(ERROR) << "Failed to begin the transaction.";
699 return;
700 }
701
702 MediaHistoryTableBase* tables[] = {
703 playback_table_.get(),
704 session_table_.get(),
705 };
706
707 std::set<url::Origin> origins_with_deletions;
708 for (auto& url : urls) {
709 origins_with_deletions.insert(url::Origin::Create(url));
710
711 for (auto* table : tables) {
712 if (!table->DeleteURL(url)) {
713 DB()->RollbackTransaction();
714 return;
715 }
716 }
717 }
718
719 for (auto& origin : origins_with_deletions) {
720 if (!origin_table_->RecalculateAggregateAudioVideoWatchTime(origin)) {
721 DB()->RollbackTransaction();
722 return;
723 }
724 }
725
726 // The mediaImages table will not be automatically cleared when we remove
727 // single sessions so we should remove them manually.
728 sql::Statement statement(DB()->GetUniqueStatement(
729 "DELETE FROM mediaImage WHERE id IN ("
730 " SELECT id FROM mediaImage LEFT JOIN sessionImage"
731 " ON sessionImage.image_id = mediaImage.id"
732 " WHERE sessionImage.session_id IS NULL)"));
733
734 if (!statement.Run()) {
735 DB()->RollbackTransaction();
736 } else {
737 DB()->CommitTransaction();
738 }
739 }
740
GetURLsInTableForTest(const std::string & table)741 std::set<GURL> MediaHistoryStore::GetURLsInTableForTest(
742 const std::string& table) {
743 std::set<GURL> urls;
744
745 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
746 if (!CanAccessDatabase())
747 return urls;
748
749 sql::Statement statement(DB()->GetUniqueStatement(
750 base::StringPrintf("SELECT url from %s", table.c_str()).c_str()));
751
752 while (statement.Step()) {
753 urls.insert(GURL(statement.ColumnString(0)));
754 }
755
756 DCHECK(statement.Succeeded());
757 return urls;
758 }
759
DiscoverMediaFeed(const GURL & url)760 void MediaHistoryStore::DiscoverMediaFeed(const GURL& url) {
761 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
762 if (!CanAccessDatabase())
763 return;
764
765 if (!feeds_table_)
766 return;
767
768 if (!DB()->BeginTransaction()) {
769 LOG(ERROR) << "Failed to begin the transaction.";
770 return;
771 }
772
773 if (!(CreateOriginId(url::Origin::Create(url)) &&
774 feeds_table_->DiscoverFeed(url))) {
775 DB()->RollbackTransaction();
776 return;
777 }
778
779 DB()->CommitTransaction();
780 }
781
StoreMediaFeedFetchResult(MediaHistoryKeyedService::MediaFeedFetchResult result)782 void MediaHistoryStore::StoreMediaFeedFetchResult(
783 MediaHistoryKeyedService::MediaFeedFetchResult result) {
784 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
785 if (!CanAccessDatabase())
786 return;
787
788 if (!feeds_table_ || !feed_items_table_)
789 return;
790
791 auto fetch_details = feeds_table_->GetFetchDetails(result.feed_id);
792 if (!fetch_details)
793 return;
794
795 // If the reset token does not match then we should store a fetch failure.
796 if (fetch_details->reset_token != result.reset_token) {
797 MediaHistoryKeyedService::MediaFeedFetchResult new_result;
798 new_result.feed_id = result.feed_id;
799 new_result.status =
800 media_feeds::mojom::FetchResult::kFailedDueToResetWhileInflight;
801 StoreMediaFeedFetchResultInternal(std::move(new_result));
802 return;
803 }
804
805 StoreMediaFeedFetchResultInternal(std::move(result));
806 }
807
StoreMediaFeedFetchResultInternal(MediaHistoryKeyedService::MediaFeedFetchResult result)808 void MediaHistoryStore::StoreMediaFeedFetchResultInternal(
809 MediaHistoryKeyedService::MediaFeedFetchResult result) {
810 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
811 if (!CanAccessDatabase())
812 return;
813
814 if (!feeds_table_ || !feed_items_table_)
815 return;
816
817 if (!DB()->BeginTransaction()) {
818 LOG(ERROR) << "Failed to begin the transaction.";
819 return;
820 }
821
822 // Remove all the items currently associated with this feed.
823 if (!feed_items_table_->DeleteItems(result.feed_id)) {
824 DB()->RollbackTransaction();
825 return;
826 }
827
828 int item_play_next_count = 0;
829 int item_content_types = 0;
830 int item_safe_count = 0;
831
832 for (auto& item : result.items) {
833 // Save each item to the table.
834 if (!feed_items_table_->SaveItem(result.feed_id, item)) {
835 DB()->RollbackTransaction();
836 return;
837 }
838
839 // If the item has a play next candidate or the user is currently watching
840 // this media then we should add it to the play next count.
841 if (item->play_next_candidate ||
842 item->action_status ==
843 media_feeds::mojom::MediaFeedItemActionStatus::kActive) {
844 item_play_next_count++;
845 }
846
847 // If the item is marked as safe then we should add it to the safe count.
848 if (item->safe_search_result ==
849 media_feeds::mojom::SafeSearchResult::kSafe) {
850 item_safe_count++;
851 }
852
853 item_content_types |= static_cast<int>(item->type);
854 }
855
856 const media_feeds::mojom::UserIdentifier* user_identifier =
857 result.user_identifier ? result.user_identifier.get() : nullptr;
858
859 // Update the metadata associated with this feed.
860 if (!feeds_table_->UpdateFeedFromFetch(
861 result.feed_id, result.status, result.was_fetched_from_cache,
862 result.items.size(), item_play_next_count, item_content_types,
863 result.logos, user_identifier, result.display_name, item_safe_count,
864 result.cookie_name_filter)) {
865 DB()->RollbackTransaction();
866 return;
867 }
868
869 if (result.status !=
870 media_feeds::mojom::FetchResult::kFailedDueToResetWhileInflight) {
871 if (!feeds_table_->ClearResetReason(result.feed_id)) {
872 DB()->RollbackTransaction();
873 return;
874 }
875 }
876
877 DB()->CommitTransaction();
878 }
879
880 MediaHistoryKeyedService::PendingSafeSearchCheckList
GetPendingSafeSearchCheckMediaFeedItems()881 MediaHistoryStore::GetPendingSafeSearchCheckMediaFeedItems() {
882 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
883
884 if (!CanAccessDatabase() || !feed_items_table_ || !feeds_table_)
885 return MediaHistoryKeyedService::PendingSafeSearchCheckList();
886
887 auto items = feeds_table_->GetPendingSafeSearchCheckItems();
888 for (auto& item : feed_items_table_->GetPendingSafeSearchCheckItems())
889 items.push_back(std::move(item));
890
891 return items;
892 }
893
StoreMediaFeedItemSafeSearchResults(std::map<MediaHistoryKeyedService::SafeSearchID,media_feeds::mojom::SafeSearchResult> results)894 void MediaHistoryStore::StoreMediaFeedItemSafeSearchResults(
895 std::map<MediaHistoryKeyedService::SafeSearchID,
896 media_feeds::mojom::SafeSearchResult> results) {
897 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
898 if (!CanAccessDatabase())
899 return;
900
901 if (!feeds_table_ || !feed_items_table_)
902 return;
903
904 if (!DB()->BeginTransaction()) {
905 LOG(ERROR) << "Failed to begin the transaction.";
906 return;
907 }
908
909 std::set<int64_t> feed_ids;
910 for (auto& entry : results) {
911 if (entry.first.first ==
912 MediaHistoryKeyedService::SafeSearchCheckedType::kFeed) {
913 if (!feeds_table_->StoreSafeSearchResult(entry.first.second,
914 entry.second)) {
915 DB()->RollbackTransaction();
916 return;
917 }
918
919 continue;
920 }
921
922 auto feed_id = feed_items_table_->StoreSafeSearchResult(entry.first.second,
923 entry.second);
924
925 if (!feed_id.has_value()) {
926 DB()->RollbackTransaction();
927 return;
928 }
929
930 feed_ids.insert(*feed_id);
931 }
932
933 for (auto& feed_id : feed_ids) {
934 if (!feeds_table_->RecalculateSafeSearchItemCount(feed_id)) {
935 DB()->RollbackTransaction();
936 return;
937 }
938 }
939
940 DB()->CommitTransaction();
941 }
942
SetCancelled()943 void MediaHistoryStore::SetCancelled() {
944 DCHECK(!db_task_runner_->RunsTasksInCurrentSequence());
945
946 cancelled_.Set();
947
948 MediaHistoryTableBase* tables[] = {
949 origin_table_.get(), playback_table_.get(), session_table_.get(),
950 session_images_table_.get(), images_table_.get(), feeds_table_.get(),
951 feed_items_table_.get(),
952 };
953
954 for (auto* table : tables) {
955 if (table)
956 table->SetCancelled();
957 }
958 }
959
IncrementMediaFeedItemsShownCount(const std::set<int64_t> feed_item_ids)960 void MediaHistoryStore::IncrementMediaFeedItemsShownCount(
961 const std::set<int64_t> feed_item_ids) {
962 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
963 if (!CanAccessDatabase())
964 return;
965
966 if (!feed_items_table_)
967 return;
968
969 if (!DB()->BeginTransaction()) {
970 LOG(ERROR) << "Failed to begin the transaction.";
971 return;
972 }
973
974 for (auto& feed_item_id : feed_item_ids) {
975 if (!feed_items_table_->IncrementShownCount(feed_item_id)) {
976 DB()->RollbackTransaction();
977 return;
978 }
979 }
980
981 DB()->CommitTransaction();
982 }
983
MarkMediaFeedItemAsClicked(const int64_t & feed_item_id)984 void MediaHistoryStore::MarkMediaFeedItemAsClicked(
985 const int64_t& feed_item_id) {
986 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
987 if (!CanAccessDatabase())
988 return;
989
990 if (!feed_items_table_)
991 return;
992
993 if (!DB()->BeginTransaction()) {
994 LOG(ERROR) << "Failed to begin the transaction.";
995 return;
996 }
997
998 if (feed_items_table_->MarkAsClicked(feed_item_id)) {
999 DB()->CommitTransaction();
1000 } else {
1001 DB()->RollbackTransaction();
1002 }
1003 }
1004
CanAccessDatabase() const1005 bool MediaHistoryStore::CanAccessDatabase() const {
1006 return !IsCancelled() && initialization_successful_ && db_ && db_->is_open();
1007 }
1008
IsCancelled() const1009 bool MediaHistoryStore::IsCancelled() const {
1010 return cancelled_.IsSet();
1011 }
1012
UpdateMediaFeedDisplayTime(const int64_t feed_id)1013 void MediaHistoryStore::UpdateMediaFeedDisplayTime(const int64_t feed_id) {
1014 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
1015 if (!initialization_successful_)
1016 return;
1017
1018 if (!feeds_table_)
1019 return;
1020
1021 if (!DB()->BeginTransaction()) {
1022 LOG(ERROR) << "Failed to begin the transaction.";
1023 return;
1024 }
1025
1026 if (!feeds_table_->UpdateDisplayTime(feed_id)) {
1027 DB()->RollbackTransaction();
1028 return;
1029 }
1030
1031 DB()->CommitTransaction();
1032 }
1033
ResetMediaFeed(const url::Origin & origin,media_feeds::mojom::ResetReason reason)1034 void MediaHistoryStore::ResetMediaFeed(const url::Origin& origin,
1035 media_feeds::mojom::ResetReason reason) {
1036 if (!CanAccessDatabase())
1037 return;
1038
1039 if (!feeds_table_ || !feed_items_table_)
1040 return;
1041
1042 // Get the feed for |origin|.
1043 base::Optional<int64_t> feed_id = feeds_table_->GetFeedForOrigin(origin);
1044 if (!feed_id.has_value())
1045 return;
1046
1047 if (!DB()->BeginTransaction()) {
1048 LOG(ERROR) << "Failed to begin the transaction.";
1049 return;
1050 }
1051
1052 if (ResetMediaFeedInternal({*feed_id}, reason)) {
1053 DB()->CommitTransaction();
1054 } else {
1055 DB()->RollbackTransaction();
1056 }
1057 }
1058
ResetMediaFeedDueToCookies(const url::Origin & origin,const bool include_subdomains,const std::string & name,const net::CookieChangeCause & cause)1059 void MediaHistoryStore::ResetMediaFeedDueToCookies(
1060 const url::Origin& origin,
1061 const bool include_subdomains,
1062 const std::string& name,
1063 const net::CookieChangeCause& cause) {
1064 if (!CanAccessDatabase())
1065 return;
1066
1067 if (!feeds_table_ || !feed_items_table_)
1068 return;
1069
1070 // Get all the feeds for |origin| possibly including subdomains.
1071 std::set<int64_t> feed_ids;
1072
1073 if (include_subdomains)
1074 feed_ids = feeds_table_->GetFeedsForOriginSubdomain(origin);
1075
1076 base::Optional<int64_t> feed_id = feeds_table_->GetFeedForOrigin(origin);
1077 if (feed_id.has_value())
1078 feed_ids.insert(*feed_id);
1079
1080 if (feed_ids.empty())
1081 return;
1082
1083 if (!DB()->BeginTransaction()) {
1084 LOG(ERROR) << "Failed to begin the transaction.";
1085 return;
1086 }
1087
1088 std::set<int64_t> feed_ids_to_reset;
1089 for (auto feed_id : feed_ids) {
1090 auto cookie_name_filter = feeds_table_->GetCookieNameFilter(feed_id);
1091
1092 // If the cookie name filter is empty then we only allow feeds to be reset
1093 // if the cookie change was from expiration.
1094 if (cookie_name_filter.empty() && IsCauseFromExpiration(cause))
1095 feed_ids_to_reset.insert(feed_id);
1096
1097 // If we have a cookie name filter and the current cookie matches that name
1098 // then we allow any type of cookie change to reset the feed because we
1099 // can be more specific.
1100 if (!cookie_name_filter.empty() && cookie_name_filter == name)
1101 feed_ids_to_reset.insert(feed_id);
1102 }
1103
1104 if (ResetMediaFeedInternal(feed_ids_to_reset,
1105 media_feeds::mojom::ResetReason::kCookies)) {
1106 DB()->CommitTransaction();
1107 } else {
1108 DB()->RollbackTransaction();
1109 }
1110 }
1111
ResetMediaFeedDueToCacheClearing(const base::Time & start_time,const base::Time & end_time,MediaHistoryKeyedService::CacheClearingFilter filter)1112 void MediaHistoryStore::ResetMediaFeedDueToCacheClearing(
1113 const base::Time& start_time,
1114 const base::Time& end_time,
1115 MediaHistoryKeyedService::CacheClearingFilter filter) {
1116 if (!CanAccessDatabase())
1117 return;
1118
1119 if (!feeds_table_)
1120 return;
1121
1122 if (!DB()->BeginTransaction()) {
1123 LOG(ERROR) << "Failed to begin the transaction.";
1124 return;
1125 }
1126
1127 const auto start_time_s = start_time.ToDeltaSinceWindowsEpoch().InSeconds();
1128 const auto end_time_s = end_time.ToDeltaSinceWindowsEpoch().InSeconds();
1129
1130 sql::Statement statement(DB()->GetCachedStatement(
1131 SQL_FROM_HERE,
1132 "SELECT id, url FROM mediaFeed WHERE last_fetch_time_s >= ? AND "
1133 "last_fetch_time_s <= ?"));
1134 statement.BindInt64(0, start_time_s);
1135 statement.BindInt64(1, end_time_s);
1136
1137 std::set<int64_t> feed_ids;
1138 while (statement.Step()) {
1139 GURL url(statement.ColumnString(1));
1140
1141 if (!filter.is_null() && !filter.Run(url))
1142 continue;
1143
1144 feed_ids.insert(statement.ColumnInt64(0));
1145 }
1146
1147 if (ResetMediaFeedInternal(feed_ids,
1148 media_feeds::mojom::ResetReason::kCache)) {
1149 DB()->CommitTransaction();
1150 } else {
1151 DB()->RollbackTransaction();
1152 }
1153 }
1154
ResetMediaFeedInternal(const std::set<int64_t> & feed_ids,media_feeds::mojom::ResetReason reason)1155 bool MediaHistoryStore::ResetMediaFeedInternal(
1156 const std::set<int64_t>& feed_ids,
1157 media_feeds::mojom::ResetReason reason) {
1158 DCHECK_LT(0, DB()->transaction_nesting());
1159 if (!CanAccessDatabase())
1160 return false;
1161
1162 for (auto& feed_id : feed_ids) {
1163 // Remove all the items currently associated with this feed.
1164 if (!feeds_table_->Reset(feed_id, reason))
1165 return false;
1166
1167 // Remove all the items currently associated with this feed.
1168 if (!feed_items_table_->DeleteItems(feed_id))
1169 return false;
1170 }
1171
1172 return true;
1173 }
1174
DeleteMediaFeed(const int64_t feed_id)1175 void MediaHistoryStore::DeleteMediaFeed(const int64_t feed_id) {
1176 if (!CanAccessDatabase())
1177 return;
1178
1179 if (!feeds_table_)
1180 return;
1181
1182 if (!DB()->BeginTransaction()) {
1183 LOG(ERROR) << "Failed to begin the transaction.";
1184 return;
1185 }
1186
1187 if (!feeds_table_->Delete(feed_id)) {
1188 DB()->RollbackTransaction();
1189 return;
1190 }
1191
1192 DB()->CommitTransaction();
1193 }
1194
1195 base::Optional<MediaHistoryKeyedService::MediaFeedFetchDetails>
GetMediaFeedFetchDetails(const int64_t feed_id)1196 MediaHistoryStore::GetMediaFeedFetchDetails(const int64_t feed_id) {
1197 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
1198 if (!CanAccessDatabase() || !feeds_table_)
1199 return base::nullopt;
1200
1201 return feeds_table_->GetFetchDetails(feed_id);
1202 }
1203
UpdateFeedUserStatus(const int64_t feed_id,media_feeds::mojom::FeedUserStatus status)1204 void MediaHistoryStore::UpdateFeedUserStatus(
1205 const int64_t feed_id,
1206 media_feeds::mojom::FeedUserStatus status) {
1207 if (!CanAccessDatabase())
1208 return;
1209
1210 if (!feeds_table_)
1211 return;
1212
1213 if (!DB()->BeginTransaction()) {
1214 DLOG(ERROR) << "Failed to begin the transaction.";
1215 return;
1216 }
1217
1218 if (!feeds_table_->UpdateFeedUserStatus(feed_id, status)) {
1219 DB()->RollbackTransaction();
1220 return;
1221 }
1222
1223 DB()->CommitTransaction();
1224 }
1225
SetKaleidoscopeData(media::mojom::GetCollectionsResponsePtr data,const std::string & gaia_id)1226 void MediaHistoryStore::SetKaleidoscopeData(
1227 media::mojom::GetCollectionsResponsePtr data,
1228 const std::string& gaia_id) {
1229 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
1230 if (!CanAccessDatabase())
1231 return;
1232
1233 if (!DB()->BeginTransaction()) {
1234 DLOG(ERROR) << "Failed to begin the transaction.";
1235 return;
1236 }
1237
1238 if (!kaleidoscope_table_->Set(std::move(data), gaia_id)) {
1239 DB()->RollbackTransaction();
1240 return;
1241 }
1242
1243 DB()->CommitTransaction();
1244 }
1245
GetKaleidoscopeData(const std::string & gaia_id)1246 media::mojom::GetCollectionsResponsePtr MediaHistoryStore::GetKaleidoscopeData(
1247 const std::string& gaia_id) {
1248 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
1249 if (!CanAccessDatabase())
1250 return nullptr;
1251
1252 if (!DB()->BeginTransaction()) {
1253 DLOG(ERROR) << "Failed to begin the transaction.";
1254 return nullptr;
1255 }
1256
1257 auto out = kaleidoscope_table_->Get(gaia_id);
1258 DB()->CommitTransaction();
1259 return out;
1260 }
1261
DeleteKaleidoscopeData()1262 void MediaHistoryStore::DeleteKaleidoscopeData() {
1263 DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
1264 if (!CanAccessDatabase())
1265 return;
1266
1267 if (!DB()->BeginTransaction()) {
1268 DLOG(ERROR) << "Failed to begin the transaction.";
1269 return;
1270 }
1271
1272 if (!kaleidoscope_table_->Delete()) {
1273 DB()->RollbackTransaction();
1274 return;
1275 }
1276
1277 DB()->CommitTransaction();
1278 }
1279
1280 } // namespace media_history
1281