| // Copyright 2018 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 "chromeos/components/drivefs/fake_drivefs.h" |
| |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/no_destructor.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "chromeos/components/drivefs/drivefs_util.h" |
| #include "chromeos/components/drivefs/mojom/drivefs.mojom.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_cros_disks_client.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/base/mime_util.h" |
| #include "url/gurl.h" |
| |
| namespace drivefs { |
| namespace { |
| |
| std::vector<std::pair<base::RepeatingCallback<std::string()>, |
| base::WeakPtr<FakeDriveFs>>>& |
| GetRegisteredFakeDriveFsIntances() { |
| static base::NoDestructor<std::vector<std::pair< |
| base::RepeatingCallback<std::string()>, base::WeakPtr<FakeDriveFs>>>> |
| registered_fake_drivefs_instances; |
| return *registered_fake_drivefs_instances; |
| } |
| |
| base::FilePath MaybeMountDriveFs( |
| const std::string& source_path, |
| const std::vector<std::string>& mount_options) { |
| GURL source_url(source_path); |
| DCHECK(source_url.is_valid()); |
| if (source_url.scheme() != "drivefs") { |
| return {}; |
| } |
| std::string datadir_suffix; |
| for (const auto& option : mount_options) { |
| if (base::StartsWith(option, "datadir=", base::CompareCase::SENSITIVE)) { |
| auto datadir = |
| base::FilePath(base::StringPiece(option).substr(strlen("datadir="))); |
| CHECK(datadir.IsAbsolute()); |
| CHECK(!datadir.ReferencesParent()); |
| datadir_suffix = datadir.BaseName().value(); |
| break; |
| } |
| } |
| CHECK(!datadir_suffix.empty()); |
| for (auto& registration : GetRegisteredFakeDriveFsIntances()) { |
| std::string account_id = registration.first.Run(); |
| if (registration.second && !account_id.empty() && |
| account_id == datadir_suffix) { |
| return registration.second->mount_path(); |
| } |
| } |
| NOTREACHED() << datadir_suffix; |
| return {}; |
| } |
| |
| } // namespace |
| |
| FakeDriveFsBootstrapListener::FakeDriveFsBootstrapListener( |
| mojo::PendingRemote<drivefs::mojom::DriveFsBootstrap> bootstrap) |
| : bootstrap_(std::move(bootstrap)) {} |
| |
| FakeDriveFsBootstrapListener::~FakeDriveFsBootstrapListener() = default; |
| |
| void FakeDriveFsBootstrapListener::SendInvitationOverPipe(base::ScopedFD) {} |
| |
| mojo::PendingRemote<mojom::DriveFsBootstrap> |
| FakeDriveFsBootstrapListener::bootstrap() { |
| return std::move(bootstrap_); |
| } |
| |
| struct FakeDriveFs::FileMetadata { |
| std::string mime_type; |
| bool pinned = false; |
| bool hosted = false; |
| bool shared = false; |
| std::string original_name; |
| mojom::Capabilities capabilities; |
| mojom::FolderFeature folder_feature; |
| }; |
| |
| class FakeDriveFs::SearchQuery : public mojom::SearchQuery { |
| public: |
| SearchQuery(base::WeakPtr<FakeDriveFs> drive_fs, |
| drivefs::mojom::QueryParametersPtr params) |
| : drive_fs_(std::move(drive_fs)), params_(std::move(params)) {} |
| |
| private: |
| void GetNextPage(GetNextPageCallback callback) override { |
| if (!drive_fs_) { |
| std::move(callback).Run(drive::FileError::FILE_ERROR_ABORT, {}); |
| } else { |
| // Default implementation: just search for a file name. |
| callback_ = std::move(callback); |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(&SearchQuery::SearchFiles, drive_fs_->mount_path()), |
| base::BindOnce(&SearchQuery::GetMetadata, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| static std::vector<drivefs::mojom::QueryItemPtr> SearchFiles( |
| const base::FilePath& mount_path) { |
| std::vector<drivefs::mojom::QueryItemPtr> results; |
| base::FileEnumerator walker( |
| mount_path, true, |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
| for (auto file = walker.Next(); !file.empty(); file = walker.Next()) { |
| auto item = drivefs::mojom::QueryItem::New(); |
| item->path = base::FilePath("/"); |
| CHECK(mount_path.AppendRelativePath(file, &item->path)); |
| results.push_back(std::move(item)); |
| } |
| return results; |
| } |
| |
| void GetMetadata(std::vector<drivefs::mojom::QueryItemPtr> results) { |
| if (!drive_fs_) { |
| std::move(callback_).Run(drive::FileError::FILE_ERROR_ABORT, {}); |
| } else { |
| results_ = std::move(results); |
| pending_callbacks_ = results_.size() + 1; |
| for (size_t i = 0; i < results_.size(); ++i) { |
| drive_fs_->GetMetadata( |
| results_[i]->path, |
| base::BindOnce(&SearchQuery::OnMetadata, |
| weak_ptr_factory_.GetWeakPtr(), i)); |
| } |
| OnComplete(); |
| } |
| } |
| |
| void OnMetadata(size_t index, |
| drive::FileError error, |
| drivefs::mojom::FileMetadataPtr metadata) { |
| if (error == drive::FileError::FILE_ERROR_OK) { |
| results_[index]->metadata = std::move(metadata); |
| } |
| OnComplete(); |
| } |
| |
| void OnComplete() { |
| if (--pending_callbacks_ == 0) { |
| auto query = base::ToLowerASCII( |
| params_->title.value_or(params_->text_content.value_or(""))); |
| |
| // Filter out non-matching results. |
| base::EraseIf(results_, [=](const auto& item_ptr) { |
| if (!item_ptr->metadata) { |
| return true; |
| } |
| const base::FilePath path = item_ptr->path; |
| const drivefs::mojom::FileMetadata* metadata = item_ptr->metadata.get(); |
| if (!query.empty()) { |
| return base::ToLowerASCII(path.BaseName().value()).find(query) == |
| std::string::npos; |
| } |
| if (params_->available_offline) { |
| return !metadata->available_offline && IsLocal(metadata->type); |
| } |
| if (params_->shared_with_me) { |
| return !metadata->shared; |
| } |
| if (params_->mime_type.has_value()) { |
| return !net::MatchesMimeType(params_->mime_type.value() + "/*", |
| metadata->content_mime_type); |
| } |
| return false; |
| }); |
| |
| const auto sort_direction = params_->sort_direction; |
| switch (params_->sort_field) { |
| case mojom::QueryParameters::SortField::kLastModified: |
| case mojom::QueryParameters::SortField::kLastViewedByMe: |
| std::sort( |
| results_.begin(), results_.end(), |
| [sort_direction](const auto& a, const auto& b) { |
| auto a_fields = std::tie(a->metadata->last_viewed_by_me_time, |
| a->metadata->modification_time); |
| auto b_fields = std::tie(b->metadata->last_viewed_by_me_time, |
| b->metadata->modification_time); |
| if (sort_direction == |
| mojom::QueryParameters::SortDirection::kAscending) { |
| return a_fields < b_fields; |
| } |
| return b_fields < a_fields; |
| }); |
| break; |
| |
| case mojom::QueryParameters::SortField::kFileSize: |
| NOTIMPLEMENTED(); |
| break; |
| |
| case mojom::QueryParameters::SortField::kNone: |
| break; |
| } |
| |
| auto page_size = base::saturated_cast<size_t>(params_->page_size); |
| if (results_.size() > page_size) { |
| results_.resize(page_size); |
| } |
| std::move(callback_).Run(drive::FileError::FILE_ERROR_OK, |
| {std::move(results_)}); |
| } |
| } |
| |
| base::WeakPtr<FakeDriveFs> drive_fs_; |
| mojom::QueryParametersPtr params_; |
| GetNextPageCallback callback_; |
| std::vector<drivefs::mojom::QueryItemPtr> results_; |
| size_t pending_callbacks_ = 0; |
| |
| base::WeakPtrFactory<SearchQuery> weak_ptr_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(SearchQuery); |
| }; |
| |
| FakeDriveFs::FakeDriveFs(const base::FilePath& mount_path) |
| : mount_path_(mount_path) { |
| CHECK(mount_path.IsAbsolute()); |
| CHECK(!mount_path.ReferencesParent()); |
| } |
| |
| FakeDriveFs::~FakeDriveFs() = default; |
| |
| void FakeDriveFs::RegisterMountingForAccountId( |
| base::RepeatingCallback<std::string()> account_id_getter) { |
| chromeos::DBusThreadManager* dbus_thread_manager = |
| chromeos::DBusThreadManager::Get(); |
| static_cast<chromeos::FakeCrosDisksClient*>( |
| dbus_thread_manager->GetCrosDisksClient()) |
| ->AddCustomMountPointCallback(base::BindRepeating(&MaybeMountDriveFs)); |
| |
| GetRegisteredFakeDriveFsIntances().emplace_back(std::move(account_id_getter), |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| std::unique_ptr<drivefs::DriveFsBootstrapListener> |
| FakeDriveFs::CreateMojoListener() { |
| delegate_.reset(); |
| pending_delegate_receiver_ = delegate_.BindNewPipeAndPassReceiver(); |
| delegate_->OnMounted(); |
| |
| bootstrap_receiver_.reset(); |
| return std::make_unique<FakeDriveFsBootstrapListener>( |
| bootstrap_receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| void FakeDriveFs::SetMetadata(const base::FilePath& path, |
| const std::string& mime_type, |
| const std::string& original_name, |
| bool pinned, |
| bool shared, |
| const mojom::Capabilities& capabilities, |
| const mojom::FolderFeature& folder_feature) { |
| auto& stored_metadata = metadata_[path]; |
| stored_metadata.mime_type = mime_type; |
| stored_metadata.original_name = original_name; |
| stored_metadata.hosted = (original_name != path.BaseName().value()); |
| stored_metadata.capabilities = capabilities; |
| stored_metadata.folder_feature = folder_feature; |
| if (pinned) { |
| stored_metadata.pinned = true; |
| } |
| if (shared) { |
| stored_metadata.shared = true; |
| } |
| } |
| |
| void FakeDriveFs::Init( |
| drivefs::mojom::DriveFsConfigurationPtr config, |
| mojo::PendingReceiver<drivefs::mojom::DriveFs> receiver, |
| mojo::PendingRemote<drivefs::mojom::DriveFsDelegate> delegate) { |
| { |
| base::ScopedAllowBlockingForTesting allow_io; |
| CHECK(base::CreateDirectory(mount_path_.Append(".Trash"))); |
| } |
| mojo::FusePipes(std::move(pending_delegate_receiver_), std::move(delegate)); |
| receiver_.reset(); |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| void FakeDriveFs::GetMetadata(const base::FilePath& path, |
| GetMetadataCallback callback) { |
| base::FilePath absolute_path = mount_path_; |
| CHECK(base::FilePath("/").AppendRelativePath(path, &absolute_path)); |
| base::File::Info info; |
| { |
| base::ScopedAllowBlockingForTesting allow_io; |
| if (!base::GetFileInfo(absolute_path, &info)) { |
| std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND, nullptr); |
| return; |
| } |
| } |
| auto metadata = drivefs::mojom::FileMetadata::New(); |
| metadata->size = info.size; |
| metadata->modification_time = info.last_modified; |
| metadata->last_viewed_by_me_time = info.last_accessed; |
| |
| const auto& stored_metadata = metadata_[path]; |
| metadata->pinned = stored_metadata.pinned; |
| metadata->available_offline = stored_metadata.pinned; |
| metadata->shared = stored_metadata.shared; |
| |
| metadata->content_mime_type = stored_metadata.mime_type; |
| metadata->type = stored_metadata.hosted |
| ? mojom::FileMetadata::Type::kHosted |
| : info.is_directory |
| ? mojom::FileMetadata::Type::kDirectory |
| : mojom::FileMetadata::Type::kFile; |
| |
| base::StringPiece prefix; |
| if (stored_metadata.hosted) { |
| prefix = "https://document_alternate_link/"; |
| } else if (info.is_directory) { |
| prefix = "https://folder_alternate_link/"; |
| } else { |
| prefix = "https://file_alternate_link/"; |
| } |
| std::string suffix = stored_metadata.original_name.empty() |
| ? path.BaseName().value() |
| : stored_metadata.original_name; |
| metadata->alternate_url = GURL(base::StrCat({prefix, suffix})).spec(); |
| metadata->capabilities = stored_metadata.capabilities.Clone(); |
| |
| std::move(callback).Run(drive::FILE_ERROR_OK, std::move(metadata)); |
| } |
| |
| void FakeDriveFs::SetPinned(const base::FilePath& path, |
| bool pinned, |
| SetPinnedCallback callback) { |
| metadata_[path].pinned = pinned; |
| std::move(callback).Run(drive::FILE_ERROR_OK); |
| } |
| |
| void FakeDriveFs::UpdateNetworkState(bool pause_syncing, bool is_offline) {} |
| |
| void FakeDriveFs::ResetCache(ResetCacheCallback callback) { |
| std::move(callback).Run(drive::FILE_ERROR_OK); |
| } |
| |
| void FakeDriveFs::GetThumbnail(const base::FilePath& path, |
| bool crop_to_square, |
| GetThumbnailCallback callback) { |
| std::move(callback).Run(base::nullopt); |
| } |
| |
| void FakeDriveFs::CopyFile(const base::FilePath& source, |
| const base::FilePath& target, |
| CopyFileCallback callback) { |
| base::ScopedAllowBlockingForTesting allow_io; |
| base::FilePath source_absolute_path = mount_path_; |
| base::FilePath target_absolute_path = mount_path_; |
| CHECK(base::FilePath("/").AppendRelativePath(source, &source_absolute_path)); |
| CHECK(base::FilePath("/").AppendRelativePath(target, &target_absolute_path)); |
| |
| base::File::Info source_info; |
| if (!base::GetFileInfo(source_absolute_path, &source_info)) { |
| std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| if (source_info.is_directory) { |
| std::move(callback).Run(drive::FILE_ERROR_NOT_A_FILE); |
| return; |
| } |
| |
| base::File::Info target_directory_info; |
| if (!base::GetFileInfo(target_absolute_path.DirName(), |
| &target_directory_info)) { |
| std::move(callback).Run(drive::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| if (!target_directory_info.is_directory) { |
| std::move(callback).Run(drive::FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| |
| if (base::PathExists(target_absolute_path)) { |
| std::move(callback).Run(drive::FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| |
| if (!base::CopyFile(source_absolute_path, target_absolute_path)) { |
| std::move(callback).Run(drive::FILE_ERROR_FAILED); |
| return; |
| } |
| metadata_[target_absolute_path] = metadata_[source_absolute_path]; |
| std::move(callback).Run(drive::FILE_ERROR_OK); |
| } |
| |
| void FakeDriveFs::StartSearchQuery( |
| mojo::PendingReceiver<drivefs::mojom::SearchQuery> receiver, |
| drivefs::mojom::QueryParametersPtr query_params) { |
| auto search_query = std::make_unique<SearchQuery>(weak_factory_.GetWeakPtr(), |
| std::move(query_params)); |
| mojo::MakeSelfOwnedReceiver(std::move(search_query), std::move(receiver)); |
| } |
| |
| void FakeDriveFs::FetchAllChangeLogs() {} |
| |
| void FakeDriveFs::FetchChangeLog( |
| std::vector<mojom::FetchChangeLogOptionsPtr> options) {} |
| |
| void FakeDriveFs::SendNativeMessageRequest( |
| const std::string& request, |
| SendNativeMessageRequestCallback callback) { |
| std::move(callback).Run(drive::FILE_ERROR_SERVICE_UNAVAILABLE, ""); |
| } |
| |
| void FakeDriveFs::SetStartupArguments(const std::string& arguments, |
| SetStartupArgumentsCallback callback) { |
| std::move(callback).Run(false); |
| } |
| |
| void FakeDriveFs::GetStartupArguments(GetStartupArgumentsCallback callback) { |
| std::move(callback).Run(""); |
| } |
| |
| void FakeDriveFs::SetTracingEnabled(bool enabled) {} |
| |
| void FakeDriveFs::SetNetworkingEnabled(bool enabled) {} |
| |
| void FakeDriveFs::ForcePauseSyncing(bool enable) {} |
| |
| void FakeDriveFs::DumpAccountSettings() {} |
| |
| void FakeDriveFs::LoadAccountSettings() {} |
| |
| } // namespace drivefs |