| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/file_suggest/drive_file_suggestion_provider.h" |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/drive/drive_integration_service.h" |
| #include "chrome/browser/ash/drive/drive_integration_service_factory.h" |
| #include "chrome/browser/ash/file_suggest/file_suggest_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/application_locale_storage/application_locale_storage.h" |
| #include "components/drive/drive_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/storage_partition.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The minimum number of results required to keep using the short delay. This |
| // means that results are refreshed more often if there are enough high-quality |
| // results returned. |
| constexpr size_t kShortDelayQuota = 3u; |
| constexpr base::TimeDelta kMaxLastModifiedTime = base::Days(8); |
| |
| // Given an absolute path representing a file in the user's Drive, returns a |
| // reparented version of the path within the user's drive fs mount. |
| base::FilePath ReparentToDriveMount( |
| const base::FilePath& path, |
| const drive::DriveIntegrationService* drive_service) { |
| DCHECK(!path.IsAbsolute()); |
| return drive_service->GetMountPointPath().Append(path.value()); |
| } |
| |
| // Sets on the specified profile whether to use a long delay duration in the |
| // query for drive file suggest data. |
| void SetUseLongDelayInDriveSuggestQuery(Profile* profile, bool use_long_delay) { |
| profile->GetPrefs()->SetBoolean(ash::prefs::kLauncherUseLongContinueDelay, |
| use_long_delay); |
| } |
| |
| // Filters out the files that exceed the max last modified time. |
| std::vector<FileSuggestData> FilterSuggestResultsByTime( |
| std::vector<FileSuggestData> suggest_results, |
| base::TimeDelta max_last_modified_time) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| |
| std::vector<FileSuggestData> filtered_results; |
| const base::Time now = base::Time::Now(); |
| for (const auto& suggest_result : suggest_results) { |
| base::File::Info info; |
| const auto& path = suggest_result.file_path; |
| if (!base::PathExists(path) || !base::GetFileInfo(path, &info) || |
| now - info.last_modified > max_last_modified_time) { |
| // Filter out this suggestion data. |
| continue; |
| } |
| |
| filtered_results.push_back(suggest_result); |
| } |
| |
| return filtered_results; |
| } |
| |
| } // namespace |
| |
| DriveFileSuggestionProvider::DriveFileSuggestionProvider( |
| const ApplicationLocaleStorage* application_locale_storage, |
| Profile* profile, |
| base::RepeatingCallback<void(FileSuggestionType)> notify_update_callback) |
| : FileSuggestionProvider(notify_update_callback), |
| profile_(profile), |
| drive_service_( |
| drive::DriveIntegrationServiceFactory::GetInstance()->GetForProfile( |
| profile_)), |
| item_suggest_cache_(std::make_unique<ItemSuggestCache>( |
| application_locale_storage->Get(), |
| profile, |
| profile->GetDefaultStoragePartition() |
| ->GetURLLoaderFactoryForBrowserProcess())), |
| drive_file_max_last_modified_time_(kMaxLastModifiedTime), |
| result_filter_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::USER_BLOCKING, base::MayBlock(), |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // It's safe to use Unretained(this) by contract of the |
| // CallbackListSubscription. |
| item_suggest_subscription_ = |
| item_suggest_cache_->RegisterCallback(base::BindRepeating( |
| &DriveFileSuggestionProvider::NotifySuggestionUpdate, |
| base::Unretained(this), FileSuggestionType::kDriveFile)); |
| } |
| |
| DriveFileSuggestionProvider::~DriveFileSuggestionProvider() = default; |
| |
| void DriveFileSuggestionProvider::GetSuggestFileData( |
| GetSuggestFileDataCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const bool has_active_validation = |
| !on_drive_results_ready_callback_list_.empty(); |
| |
| // Add `callback` to the waiting list. |
| on_drive_results_ready_callback_list_.AddUnsafe(std::move(callback)); |
| |
| // Return early if there is an active validation. `callback` will run when |
| // validation completes. |
| if (has_active_validation) { |
| return; |
| } |
| |
| // If there is not any available drive service, return early. |
| if (!drive_service_ || !drive_service_->IsMounted()) { |
| EndDriveFilePathValidation(DriveSuggestValidationStatus::kDriveFSNotMounted, |
| /*suggest_results=*/std::nullopt); |
| return; |
| } else if (profile_->GetPrefs()->GetBoolean(drive::prefs::kDisableDrive)) { |
| EndDriveFilePathValidation(DriveSuggestValidationStatus::kDriveDisabled, |
| /*suggest_results=*/std::nullopt); |
| return; |
| } |
| |
| const std::optional<ItemSuggestCache::Results> results_before_validation = |
| item_suggest_cache_->GetResults(); |
| |
| // If there is no available data to validate, return early. |
| if (!results_before_validation || |
| results_before_validation->results.empty()) { |
| // An empty but non-null value indicates that the cache was updated |
| // successfully, and no results were returned. |
| if (results_before_validation && |
| results_before_validation->results.empty()) { |
| SetUseLongDelayInDriveSuggestQuery(profile_, true); |
| } |
| |
| EndDriveFilePathValidation(DriveSuggestValidationStatus::kNoResults, |
| /*suggest_results=*/std::nullopt); |
| return; |
| } |
| |
| std::vector<std::string> item_ids; |
| for (const auto& result : results_before_validation->results) { |
| item_ids.push_back(result.id); |
| } |
| |
| // Validate the drive cache results whenever drive file suggest data is |
| // fetched to guarantee the file path validity. |
| drive_service_->LocateFilesByItemIds( |
| item_ids, |
| base::BindOnce(&DriveFileSuggestionProvider::OnDriveFilePathsLocated, |
| weak_factory_.GetWeakPtr(), |
| results_before_validation->results)); |
| } |
| |
| void DriveFileSuggestionProvider::MaybeUpdateItemSuggestCache( |
| base::PassKey<FileSuggestKeyedService>) { |
| item_suggest_cache_->MaybeUpdateCache(); |
| } |
| |
| void DriveFileSuggestionProvider::EndDriveFilePathValidation( |
| DriveSuggestValidationStatus validation_status, |
| const std::optional<std::vector<FileSuggestData>>& suggest_results) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // If there aren't enough results, use a long delay and vice versa. |
| if (validation_status == DriveSuggestValidationStatus::kOk) { |
| SetUseLongDelayInDriveSuggestQuery( |
| profile_, suggest_results->size() < kShortDelayQuota); |
| } |
| |
| // TODO(https://crbug.com/1356344): when the refactoring code is stable, |
| // remove the obsolete histogram originally used by `ZeroStateDriveProvider`. |
| base::UmaHistogramEnumeration( |
| "Ash.Search.DriveFileSuggestDataValidation.Status", validation_status); |
| |
| // Notify observers of the fetched drive suggest results. |
| on_drive_results_ready_callback_list_.Notify(suggest_results); |
| DCHECK(on_drive_results_ready_callback_list_.empty()); |
| } |
| |
| void DriveFileSuggestionProvider::OnDriveFilePathsLocated( |
| std::vector<ItemSuggestCache::Result> raw_suggest_results, |
| std::optional<std::vector<drivefs::mojom::FilePathOrErrorPtr>> paths) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // If validation fails, return early. |
| if (!paths) { |
| EndDriveFilePathValidation( |
| DriveSuggestValidationStatus::kPathLocationFailed, |
| /*suggest_results=*/std::nullopt); |
| return; |
| } |
| |
| DCHECK_EQ(raw_suggest_results.size(), paths->size()); |
| |
| std::vector<FileSuggestData> suggest_results; |
| for (size_t index = 0; index < paths->size(); ++index) { |
| const auto& path_or_error = paths->at(index); |
| |
| // Fail to validate the file path at `index`, so skip this loop iteration. |
| if (!path_or_error->is_path()) { |
| continue; |
| } |
| |
| std::optional<std::u16string> reason; |
| if (raw_suggest_results[index].prediction_reason) { |
| reason = base::UTF8ToUTF16( |
| raw_suggest_results[index].prediction_reason.value()); |
| } |
| suggest_results.emplace_back( |
| FileSuggestionType::kDriveFile, |
| ReparentToDriveMount(path_or_error->get_path(), drive_service_), |
| /*title=*/std::nullopt, reason, |
| /*modified_time=*/std::nullopt, |
| /*viewed_time=*/std::nullopt, |
| /*shared_time=*/std::nullopt, |
| /*score=*/std::nullopt, |
| /*drive_file_id=*/std::nullopt, |
| /*icon_url=*/std::nullopt); |
| } |
| |
| // Validation fails on each file, so return early. |
| if (suggest_results.empty()) { |
| EndDriveFilePathValidation(DriveSuggestValidationStatus::kAllFilesErrored, |
| /*suggest_results=*/std::nullopt); |
| return; |
| } |
| |
| result_filter_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&FilterSuggestResultsByTime, suggest_results, |
| drive_file_max_last_modified_time_), |
| base::BindOnce(&DriveFileSuggestionProvider::EndDriveFilePathValidation, |
| weak_factory_.GetWeakPtr(), |
| DriveSuggestValidationStatus::kOk)); |
| } |
| |
| } // namespace ash |