blob: 0c01bbbfa54e17331abb8ee81743dae5e1755558 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/user_data_importer/content/stable_portability_data_importer.h"
#include "base/check_deref.h"
#include "base/files/file.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected_macros.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/url_row.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_data_importer/utility/bookmark_util.h"
#include "components/user_data_importer/utility/history_callback_from_rust.h"
#include "components/user_data_importer/utility/parsing_ffi/lib.rs.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/l10n/l10n_util.h"
namespace user_data_importer {
namespace {
std::string_view RustStringToStringView(const rust::String& rust_string) {
return std::string_view(rust_string.data(), rust_string.length());
}
std::u16string RustStringToUTF16(const rust::String& rust_string) {
return base::UTF8ToUTF16(RustStringToStringView(rust_string));
}
std::optional<history::URLRow> ConvertToURLRow(
const user_data_importer::StablePortabilityHistoryEntry& history_entry) {
GURL gurl(RustStringToStringView(history_entry.url));
if (!gurl.is_valid()) {
return std::nullopt;
}
history::URLRow url_row(gurl);
url_row.set_title(RustStringToUTF16(history_entry.title));
url_row.set_visit_count(history_entry.visit_count);
url_row.set_last_visit(
base::Time::UnixEpoch() +
base::Microseconds(history_entry.visit_time_unix_epoch_usec));
url_row.set_typed_count(history_entry.typed_count);
return url_row;
}
} // namespace
StablePortabilityDataImporter::RustHistoryCallbackForStablePortabilityFormat::
RustHistoryCallbackForStablePortabilityFormat(
TransferHistoryCallback transfer_history_callback,
user_data_importer::StablePortabilityDataImporter::ImportCallback
done_callback)
: transfer_history_callback_(std::move(transfer_history_callback)),
done_callback_(std::move(done_callback)) {}
StablePortabilityDataImporter::RustHistoryCallbackForStablePortabilityFormat::
~RustHistoryCallbackForStablePortabilityFormat() = default;
void StablePortabilityDataImporter::
RustHistoryCallbackForStablePortabilityFormat::ImportHistoryEntries(
std::unique_ptr<std::vector<StablePortabilityHistoryEntry>>
history_entries,
bool completed) {
parsed_history_entries_count_ += history_entries->size();
transfer_history_callback_.Run(std::move(*history_entries));
if (completed && done_callback_) {
std::move(done_callback_).Run(parsed_history_entries_count_);
}
}
void StablePortabilityDataImporter::
RustHistoryCallbackForStablePortabilityFormat::Fail() {
if (done_callback_) {
std::move(done_callback_).Run(-1);
}
}
StablePortabilityDataImporter::BackgroundWorker::BackgroundWorker(
std::unique_ptr<ContentBookmarkParser> bookmark_parser)
: bookmark_parser_(std::move(bookmark_parser)) {}
StablePortabilityDataImporter::BackgroundWorker::~BackgroundWorker() = default;
void StablePortabilityDataImporter::BackgroundWorker::ParseBookmarks(
base::File file,
BookmarkParser::BookmarkParsingCallback bookmarks_callback) {
bookmark_parser_->Parse(std::move(file), std::move(bookmarks_callback));
}
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
void StablePortabilityDataImporter::BackgroundWorker::ParseHistory(
base::File file,
std::unique_ptr<RustHistoryCallbackForStablePortabilityFormat> callback,
size_t import_batch_size) {
int owned_raw_fd = file.TakePlatformFile();
user_data_importer::parse_stable_portability_history(
owned_raw_fd, std::move(callback), import_batch_size);
}
#endif // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
StablePortabilityDataImporter::StablePortabilityDataImporter(
history::HistoryService* history_service,
bookmarks::BookmarkModel* bookmark_model,
ReadingListModel* reading_list_model,
std::unique_ptr<ContentBookmarkParser> bookmark_parser)
: history_service_(history_service),
bookmark_model_(bookmark_model),
reading_list_model_(reading_list_model),
metrics_recorder_(
ImporterMetricsRecorder::Source::kStablePortabilityData),
origin_sequence_task_runner_(
base::SequencedTaskRunner::GetCurrentDefault()),
background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
background_worker_(background_task_runner_, std::move(bookmark_parser)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
StablePortabilityDataImporter::~StablePortabilityDataImporter() = default;
void StablePortabilityDataImporter::ImportBookmarks(
base::File file,
ImportCallback bookmarks_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
metrics_recorder_.bookmark_metrics().OnPreparationStarted();
if (!file.IsValid()) {
metrics_recorder_.bookmark_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kNotPresent);
PostCallback(std::move(bookmarks_callback), -1);
return;
}
// Safe to bind to WeakPtr to this callback, because it will run on the same
// sequence, `origin_thread_task_runner`, where the importer was created.
auto bookmarks_parser_callback =
base::BindOnce(&StablePortabilityDataImporter::OnBookmarksParsed,
weak_factory_.GetWeakPtr(), std::move(bookmarks_callback));
auto bookmarks_parser_callback_on_thread = base::BindPostTask(
origin_sequence_task_runner_, std::move(bookmarks_parser_callback));
// Post to the thread pool the task for parsing the file. Adding the actual
// data to the user's storage should still be done on the origin sequence by
// `OnBookmarksParsed`, as that's where the `bookmark_model_` lives.
background_worker_.AsyncCall(&BackgroundWorker::ParseBookmarks)
.WithArgs(std::move(file),
std::move(bookmarks_parser_callback_on_thread));
}
void StablePortabilityDataImporter::ImportReadingList(
base::File file,
ImportCallback reading_list_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
metrics_recorder_.reading_list_metrics().OnPreparationStarted();
if (!file.IsValid()) {
metrics_recorder_.reading_list_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kNotPresent);
PostCallback(std::move(reading_list_callback), -1);
return;
}
// Safe to bind to WeakPtr to this callback, because it will run on the same
// sequence, `origin_thread_task_runner`, where the importer was created.
auto reading_list_parser_callback = base::BindOnce(
&StablePortabilityDataImporter::OnReadingListParsed,
weak_factory_.GetWeakPtr(), std::move(reading_list_callback));
auto reading_list_parser_callback_on_thread = base::BindPostTask(
origin_sequence_task_runner_, std::move(reading_list_parser_callback));
// Post to the thread pool the task for parsing the file. Adding the actual
// data to the user's storage should still be done on the origin sequence by
// `OnBookmarksParsed`, as that's where the `reading_list_model_` lives.
background_worker_.AsyncCall(&BackgroundWorker::ParseBookmarks)
.WithArgs(std::move(file),
std::move(reading_list_parser_callback_on_thread));
}
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
void StablePortabilityDataImporter::ImportHistory(
base::File file,
ImportCallback history_callback,
const size_t import_batch_size) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
metrics_recorder_.history_metrics().OnImportStarted();
if (!file.IsValid()) {
metrics_recorder_.history_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kNotPresent);
PostCallback(std::move(history_callback), -1);
return;
}
if (!history_service_) {
metrics_recorder_.history_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(history_callback), -1);
return;
}
auto transfer_history_entries_callback = base::BindPostTask(
origin_sequence_task_runner_,
base::BindRepeating(
&StablePortabilityDataImporter::TransferHistoryEntries,
weak_factory_.GetWeakPtr()));
auto done_callback =
base::BindOnce(&StablePortabilityDataImporter::OnHistoryImportCompleted,
weak_factory_.GetWeakPtr(), std::move(history_callback));
auto done_callback_on_thread = base::BindPostTask(
origin_sequence_task_runner_, std::move(done_callback));
auto rust_history_callback =
std::make_unique<RustHistoryCallbackForStablePortabilityFormat>(
std::move(transfer_history_entries_callback),
std::move(done_callback_on_thread));
background_worker_.AsyncCall(&BackgroundWorker::ParseHistory)
.WithArgs(std::move(file), std::move(rust_history_callback),
import_batch_size);
}
#endif // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
void StablePortabilityDataImporter::TransferHistoryEntries(
std::vector<StablePortabilityHistoryEntry> history_entries) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(history_service_);
history::URLRows url_rows;
url_rows.reserve(history_entries.size());
for (const auto& history_entry : history_entries) {
std::optional<history::URLRow> opt_row = ConvertToURLRow(history_entry);
if (opt_row) {
url_rows.push_back(std::move(opt_row.value()));
}
}
if (!url_rows.empty()) {
history_service_->AddPagesWithDetails(
url_rows, history::SOURCE_OS_MIGRATION_IMPORTED);
imported_history_entries_count_ += url_rows.size();
}
}
void StablePortabilityDataImporter::OnHistoryImportCompleted(
ImportCallback history_callback,
int parsed_history_entries_count) {
if (parsed_history_entries_count < 0) {
metrics_recorder_.history_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(history_callback), parsed_history_entries_count);
return;
}
metrics_recorder_.history_metrics().OnImportFinished(
imported_history_entries_count_);
metrics_recorder_.history_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kSuccess);
PostCallback(std::move(history_callback), imported_history_entries_count_);
}
void StablePortabilityDataImporter::OnBookmarksParsed(
ImportCallback bookmarks_callback,
BookmarkParser::BookmarkParsingResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!bookmark_model_) {
metrics_recorder_.bookmark_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(bookmarks_callback), -1);
return;
}
ASSIGN_OR_RETURN(BookmarkParser::ParsedBookmarks value, std::move(result),
&StablePortabilityDataImporter::OnBookmarksParsingError,
this, std::move(bookmarks_callback));
metrics_recorder_.bookmark_metrics().OnPreparationFinished(
value.bookmarks.size());
metrics_recorder_.bookmark_metrics().OnImportStarted();
// Add the parsed bookmarks to the user's storage.
size_t imported_count = ::user_data_importer::ImportBookmarks(
bookmark_model_, std::move(value.bookmarks),
l10n_util::GetStringUTF16(IDS_IMPORTED_FOLDER));
PostCallback(std::move(bookmarks_callback), imported_count);
metrics_recorder_.bookmark_metrics().OnImportFinished(imported_count);
metrics_recorder_.bookmark_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kSuccess);
}
void StablePortabilityDataImporter::OnBookmarksParsingError(
ImportCallback bookmarks_callback,
BookmarkParser::BookmarkParsingError error) {
metrics_recorder_.LogBookmarksError(ConvertBookmarkError(error));
metrics_recorder_.bookmark_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(bookmarks_callback), -1);
}
void StablePortabilityDataImporter::OnReadingListParsed(
ImportCallback reading_list_callback,
BookmarkParser::BookmarkParsingResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!reading_list_model_) {
metrics_recorder_.reading_list_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(reading_list_callback), -1);
return;
}
ASSIGN_OR_RETURN(BookmarkParser::ParsedBookmarks value, std::move(result),
&StablePortabilityDataImporter::OnReadingListParsingError,
this, std::move(reading_list_callback));
metrics_recorder_.reading_list_metrics().OnPreparationFinished(
value.bookmarks.size());
metrics_recorder_.reading_list_metrics().OnImportStarted();
// Add the parsed reading list entries to the user's storage.
size_t imported_count = ::user_data_importer::ImportReadingList(
reading_list_model_, std::move(value.bookmarks));
metrics_recorder_.reading_list_metrics().OnImportFinished(imported_count);
metrics_recorder_.reading_list_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kSuccess);
PostCallback(std::move(reading_list_callback), imported_count);
}
void StablePortabilityDataImporter::OnReadingListParsingError(
ImportCallback reading_list_callback,
BookmarkParser::BookmarkParsingError error) {
metrics_recorder_.LogReadingListError(ConvertBookmarkError(error));
metrics_recorder_.reading_list_metrics().LogOutcome(
DataTypeMetrics::ImportOutcome::kFailure);
PostCallback(std::move(reading_list_callback), -1);
}
void StablePortabilityDataImporter::PostCallback(auto callback, auto results) {
origin_sequence_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(results)));
}
} // namespace user_data_importer