| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/ash/holding_space/holding_space_util.h" |
| |
| #include "ash/public/cpp/holding_space/holding_space_constants.h" |
| #include "ash/public/cpp/holding_space/holding_space_util.h" |
| #include "ash/public/cpp/image_util.h" |
| #include "base/barrier_closure.h" |
| #include "base/containers/contains.h" |
| #include "base/files/file_path.h" |
| #include "chrome/browser/ash/drive/drive_integration_service.h" |
| #include "chrome/browser/ash/file_manager/app_id.h" |
| #include "chrome/browser/ash/file_manager/fileapi_util.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/ui/ash/thumbnail_loader.h" |
| #include "components/account_id/account_id.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| namespace holding_space_util { |
| |
| ValidityRequirement::ValidityRequirement() = default; |
| ValidityRequirement::ValidityRequirement(const ValidityRequirement&) = default; |
| ValidityRequirement::ValidityRequirement(ValidityRequirement&& other) = default; |
| |
| // Utilities ------------------------------------------------------------------- |
| |
| bool ShouldSkipPathCheck(Profile* profile, const base::FilePath& path) { |
| // Drive FS may be in the middle of restarting, so if it is enabled but not |
| // mounted, assume any files in drive are valid. |
| const auto* drive_integration_service = |
| drive::DriveIntegrationServiceFactory::FindForProfile(profile); |
| return drive_integration_service && drive_integration_service->is_enabled() && |
| !drive_integration_service->IsMounted() && |
| drive_integration_service->GetMountPointPath().IsParent(path); |
| } |
| |
| void FilePathValid(Profile* profile, |
| FilePathWithValidityRequirement file_path_with_requirement, |
| FilePathValidCallback callback) { |
| auto* user = ProfileHelper::Get()->GetUserByProfile(profile); |
| file_manager::util::GetMetadataForPath( |
| file_manager::util::GetFileManagerFileSystemContext(profile), |
| file_path_with_requirement.first, |
| storage::FileSystemOperation::GET_METADATA_FIELD_NONE, |
| base::BindOnce( |
| [](FilePathValidCallback callback, |
| FilePathWithValidityRequirement file_path_with_requirement, |
| AccountId account_id, base::File::Error result, |
| const base::File::Info& file_info) { |
| Profile* profile = |
| ProfileHelper::Get()->GetProfileByAccountId(account_id); |
| if (profile && ShouldSkipPathCheck( |
| profile, file_path_with_requirement.first)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| bool valid = true; |
| const ValidityRequirement& requirement = |
| file_path_with_requirement.second; |
| if (requirement.must_exist) |
| valid = result == base::File::Error::FILE_OK; |
| if (valid && requirement.must_be_newer_than) { |
| valid = |
| file_info.creation_time > |
| base::Time::Now() - requirement.must_be_newer_than.value(); |
| } |
| std::move(callback).Run(valid); |
| }, |
| std::move(callback), file_path_with_requirement, |
| user ? user->GetAccountId() : EmptyAccountId())); |
| } |
| |
| void PartitionFilePathsByExistence( |
| Profile* profile, |
| FilePathList file_paths, |
| PartitionFilePathsByExistenceCallback callback) { |
| FilePathsWithValidityRequirements file_paths_with_requirements; |
| for (const auto& file_path : file_paths) |
| file_paths_with_requirements.push_back({file_path, /*requirements=*/{}}); |
| PartitionFilePathsByValidity(profile, file_paths_with_requirements, |
| std::move(callback)); |
| } |
| |
| void PartitionFilePathsByValidity( |
| Profile* profile, |
| FilePathsWithValidityRequirements file_paths_with_requirements, |
| PartitionFilePathsByValidityCallback callback) { |
| if (file_paths_with_requirements.empty()) { |
| std::move(callback).Run(/*valid_file_paths=*/{}, |
| /*invalid_file_paths=*/{}); |
| return; |
| } |
| |
| auto valid_file_paths = std::make_unique<FilePathList>(); |
| auto invalid_file_paths = std::make_unique<FilePathList>(); |
| |
| auto* valid_file_paths_ptr = valid_file_paths.get(); |
| auto* invalid_file_paths_ptr = invalid_file_paths.get(); |
| |
| FilePathList file_paths; |
| for (const auto& file_path_with_requirement : file_paths_with_requirements) |
| file_paths.push_back(file_path_with_requirement.first); |
| |
| // This `barrier_closure` will be run after verifying the existence of all |
| // `file_paths`. It is expected that both `valid_file_paths` and |
| // `invalid_file_paths` will have been populated by the time of |
| // invocation. |
| base::RepeatingClosure barrier_closure = base::BarrierClosure( |
| file_paths_with_requirements.size(), |
| base::BindOnce( |
| [](FilePathList sorted_file_paths, |
| std::unique_ptr<FilePathList> valid_file_paths, |
| std::unique_ptr<FilePathList> invalid_file_paths, |
| PartitionFilePathsByValidityCallback callback) { |
| // We need to sort our partitioned vectors to match the original |
| // order that was provided at call time. This is necessary as the |
| // original order may have been lost due to race conditions when |
| // checking for file path existence. |
| auto sort = [&sorted_file_paths](FilePathList* file_paths) { |
| FilePathList temp_file_paths; |
| temp_file_paths.swap(*file_paths); |
| for (const auto& file_path : sorted_file_paths) { |
| if (base::Contains(temp_file_paths, file_path)) |
| file_paths->push_back(file_path); |
| } |
| }; |
| sort(valid_file_paths.get()); |
| sort(invalid_file_paths.get()); |
| |
| // Ownership of the partitioned vectors is passed to `callback`. |
| std::move(callback).Run(std::move(*valid_file_paths), |
| std::move(*invalid_file_paths)); |
| }, |
| /*sorted_file_paths=*/file_paths, std::move(valid_file_paths), |
| std::move(invalid_file_paths), std::move(callback))); |
| |
| // Verify existence of each `file_path`. Upon successful check of existence, |
| // each `file_path` should be pushed into either `valid_file_paths` or |
| // `invalid_file_paths` as appropriate. |
| for (const auto& file_path_with_requirement : file_paths_with_requirements) { |
| FilePathValid( |
| profile, file_path_with_requirement, |
| base::BindOnce( |
| [](base::FilePath file_path, FilePathList* valid_file_paths, |
| FilePathList* invalid_file_paths, |
| base::RepeatingClosure barrier_closure, bool exists) { |
| if (exists) |
| valid_file_paths->push_back(file_path); |
| else |
| invalid_file_paths->push_back(file_path); |
| barrier_closure.Run(); |
| }, |
| file_path_with_requirement.first, |
| base::Unretained(valid_file_paths_ptr), |
| base::Unretained(invalid_file_paths_ptr), barrier_closure)); |
| } |
| } |
| |
| GURL ResolveFileSystemUrl(Profile* profile, const base::FilePath& file_path) { |
| GURL file_system_url; |
| if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl( |
| profile, file_path, file_manager::util::GetFileManagerURL(), |
| &file_system_url)) { |
| VLOG(2) << "Unable to convert file path to File System URL."; |
| } |
| return file_system_url; |
| } |
| |
| std::unique_ptr<HoldingSpaceImage> ResolveImage( |
| ThumbnailLoader* thumbnail_loader, |
| HoldingSpaceItem::Type type, |
| const base::FilePath& file_path) { |
| return ResolveImageWithPlaceholderImageSkiaResolver( |
| thumbnail_loader, |
| /*placeholder_image_skia_resolver=*/base::NullCallback(), type, |
| file_path); |
| } |
| |
| std::unique_ptr<HoldingSpaceImage> ResolveImageWithPlaceholderImageSkiaResolver( |
| ThumbnailLoader* thumbnail_loader, |
| HoldingSpaceImage::PlaceholderImageSkiaResolver |
| placeholder_image_skia_resolver, |
| HoldingSpaceItem::Type type, |
| const base::FilePath& file_path) { |
| return std::make_unique<HoldingSpaceImage>( |
| GetMaxImageSizeForType(type), file_path, |
| /*async_bitmap_resolver=*/ |
| base::BindRepeating( |
| [](const base::WeakPtr<ThumbnailLoader>& thumbnail_loader, |
| const base::FilePath& file_path, const gfx::Size& size, |
| HoldingSpaceImage::BitmapCallback callback) { |
| if (thumbnail_loader) |
| thumbnail_loader->Load({file_path, size}, std::move(callback)); |
| }, |
| thumbnail_loader->GetWeakPtr()), |
| /*placeholder_image_skia_resolver=*/ |
| base::BindRepeating( |
| [](HoldingSpaceImage::PlaceholderImageSkiaResolver |
| placeholder_image_skia_resolver, |
| const base::FilePath& file_path, const gfx::Size& size, |
| const absl::optional<bool>& dark_background, |
| const absl::optional<bool>& is_folder) { |
| // When the initial placeholder is being created during |
| // construction, `dark_background` and `is_folder` will be absent. |
| // In that case, don't show a placeholder to minimize jank. |
| if (!dark_background.has_value() && !is_folder.has_value()) |
| return image_util::CreateEmptyImage(size); |
| // If an explicit `placeholder_image_skia_resolver` has been |
| // specified, use it to create the appropriate placeholder image. |
| if (!placeholder_image_skia_resolver.is_null()) { |
| return placeholder_image_skia_resolver.Run( |
| file_path, size, dark_background, is_folder); |
| } |
| // Otherwise, fallback to default behavior which is to create an |
| // image corresponding to the file type of the associated backing |
| // file. |
| return HoldingSpaceImage:: |
| CreateDefaultPlaceholderImageSkiaResolver() |
| .Run(file_path, size, dark_background, is_folder); |
| }, |
| placeholder_image_skia_resolver)); |
| } |
| |
| } // namespace holding_space_util |
| } // namespace ash |