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