// Copyright 2020 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/ui/ash/holding_space/holding_space_file_system_delegate.h"
#include <set>
#include <string>
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/holding_space/holding_space_constants.h"
#include "ash/public/cpp/holding_space/holding_space_file.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/ranges/algorithm.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/trash_common_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/fileapi/file_change_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace ash {
namespace {
// Returns the absolute file path for the specified `drive_path`.
// NOTE: This method requires that the `DriveIntegrationService` be mounted.
base::FilePath ConvertDrivePathToAbsoluteFilePath(
Profile* profile,
const base::FilePath& drive_path) {
const auto* drive_integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile);
if (drive_integration_service) {
base::FilePath absolute_file_path =
drive_integration_service->GetMountPointPath();
if (base::FilePath("/").AppendRelativePath(drive_path, &absolute_file_path))
return absolute_file_path;
}
NOTREACHED_IN_MIGRATION();
return base::FilePath();
}
// Returns a mojo connection to the ARC file system.
arc::ConnectionHolder<arc::mojom::FileSystemInstance,
arc::mojom::FileSystemHost>*
GetArcFileSystem() {
if (!arc::ArcServiceManager::Get())
return nullptr;
return arc::ArcServiceManager::Get()->arc_bridge_service()->file_system();
}
// Returns whether:
// * the ARC is enabled for `profile`, and
// * connection to ARC file system service is *not* established at the time.
bool IsArcFileSystemDisconnected(Profile* profile) {
return arc::IsArcPlayStoreEnabledForProfile(profile) && GetArcFileSystem() &&
!GetArcFileSystem()->IsConnected();
}
// Returns whether the item is backed by an Android file. Can be used with
// non-initialized items.
bool ItemBackedByAndroidFile(const HoldingSpaceItem* item) {
return file_manager::util::GetAndroidFilesPath().IsParent(
item->file().file_path);
}
} // namespace
// HoldingSpaceFileSystemDelegate::FileSystemWatcher ---------------------------
class HoldingSpaceFileSystemDelegate::FileSystemWatcher {
public:
explicit FileSystemWatcher(base::FilePathWatcher::Callback callback)
: callback_(callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DETACH_FROM_SEQUENCE(sequence_checker_);
}
FileSystemWatcher(const FileSystemWatcher&) = delete;
FileSystemWatcher& operator=(const FileSystemWatcher&) = delete;
~FileSystemWatcher() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
void AddWatchForParent(const base::FilePath& file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Observe the file path parent directory for changes - this reduces the
// number of inotify requests, and works well enough for detecting file
// deletion.
const base::FilePath path_to_watch = file_path.DirName();
auto it = watchers_.lower_bound(path_to_watch);
if (it == watchers_.end() || it->first != path_to_watch) {
it = watchers_.emplace_hint(it, std::piecewise_construct,
std::forward_as_tuple(path_to_watch),
std::forward_as_tuple());
it->second.Watch(
path_to_watch, base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(&FileSystemWatcher::OnFilePathChanged,
weak_factory_.GetWeakPtr()));
}
// If the target path got deleted while request to add a watcher was in
// flight, notify observers of path change immediately.
if (!base::PathExists(file_path))
OnFilePathChanged(path_to_watch, /*error=*/false);
}
void RemoveWatch(const base::FilePath& file_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
watchers_.erase(file_path);
}
base::WeakPtr<FileSystemWatcher> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
void OnFilePathChanged(const base::FilePath& file_path, bool error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// In tests `OnFilePathChanged()` events are sometimes propagated before
// `OnFileMoved()` events. Delay propagation of `OnFilePathChanged()`
// events to give `OnFileMoved()` events time to propagate.
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, base::BindOnce(callback_, file_path, error),
base::Milliseconds(1));
}
SEQUENCE_CHECKER(sequence_checker_);
base::FilePathWatcher::Callback callback_;
std::map<base::FilePath, base::FilePathWatcher> watchers_;
base::WeakPtrFactory<FileSystemWatcher> weak_factory_{this};
};
// HoldingSpaceFileSystemDelegate ----------------------------------------------
HoldingSpaceFileSystemDelegate::HoldingSpaceFileSystemDelegate(
HoldingSpaceKeyedService* service,
HoldingSpaceModel* model)
: HoldingSpaceKeyedServiceDelegate(service, model),
file_system_watcher_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
HoldingSpaceFileSystemDelegate::~HoldingSpaceFileSystemDelegate() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
weak_factory_.InvalidateWeakPtrs();
file_system_watcher_runner_->DeleteSoon(FROM_HERE,
file_system_watcher_.release());
}
void HoldingSpaceFileSystemDelegate::OnConnectionReady() {
// Schedule validity checks for android items.
for (auto& item : model()->items()) {
if (!ItemBackedByAndroidFile(item.get()))
continue;
holding_space_util::ValidityRequirement requirements;
if (item->type() != HoldingSpaceItem::Type::kPinnedFile) {
requirements.must_be_newer_than = kMaxFileAge;
}
ScheduleFilePathValidityCheck({item->file().file_path, requirements});
}
}
void HoldingSpaceFileSystemDelegate::OnFilesChanged(
const std::vector<drivefs::mojom::FileChange>& changes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// When a file is moved, a `kDelete` change will be followed by a `kCreate`
// change with the same `stable_id` in the same set of `changes`. In some
// versions of the `DriveFsHost`, `stable_id` is not supported and will always
// be `0` for backwards compatibility.
std::map<int64_t, base::FilePath> deleted_paths_by_stable_id;
std::set<base::FilePath> deleted_paths;
for (const auto& change : changes) {
// Holding space requires absolute file paths.
const base::FilePath absolute_file_path =
ConvertDrivePathToAbsoluteFilePath(profile(), change.path);
if (absolute_file_path.empty())
continue;
switch (change.type) {
case drivefs::mojom::FileChange::Type::kCreate: {
if (change.stable_id) {
// If the `kCreate` change has an associated `stable_id`, it can
// potentially be paired with a preceding `kDelete` change. In such
// cases, the change sequence actually constitutes a move rather than
// a delete of the underlying file.
auto it = deleted_paths_by_stable_id.find(change.stable_id);
if (it != deleted_paths_by_stable_id.end()) {
OnFilePathMoved(/*src=*/it->second, /*dst=*/absolute_file_path);
deleted_paths_by_stable_id.erase(it);
}
}
break;
}
case drivefs::mojom::FileChange::Type::kDelete: {
// If the change has a `stable_id` it can potentially constitute part of
// a move rather than a delete of the underlying file. Whether the
// change is move or a delete will not be known until a `kCreate` change
// follows (or is confirmed *not* to follow). When `stable_id` is absent
// it is not possible to detect a move.
if (change.stable_id)
deleted_paths_by_stable_id[change.stable_id] = absolute_file_path;
else
deleted_paths.insert(absolute_file_path);
break;
}
case drivefs::mojom::FileChange::Type::kModify: {
OnFilePathModified(absolute_file_path);
break;
}
}
}
// At this point, all `kDelete` changes in `deleted_paths_by_stable_id` are
// confirmed to be deletions, having already stripped out all changes that
// were actually constituting moves.
for (const auto& deleted_path_by_stable_id : deleted_paths_by_stable_id)
deleted_paths.insert(deleted_path_by_stable_id.second);
// Remove any holding space items backed by deleted file paths.
model()->RemoveIf(base::BindRepeating(
[](const std::set<base::FilePath>& deleted_paths,
const HoldingSpaceItem* item) {
return base::ranges::any_of(
deleted_paths, [&](const base::FilePath& deleted_path) {
return item->file().file_path == deleted_path ||
deleted_path.IsParent(item->file().file_path);
});
},
std::cref(deleted_paths)));
}
void HoldingSpaceFileSystemDelegate::Init() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_ = std::make_unique<FileSystemWatcher>(
base::BindRepeating(&HoldingSpaceFileSystemDelegate::OnFilePathChanged,
weak_factory_.GetWeakPtr()));
// Arc file system.
auto* const arc_file_system = GetArcFileSystem();
if (arc_file_system)
arc_file_system_observer_.Observe(arc_file_system);
// Drive file system.
if (drive::DriveIntegrationService* const service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile())) {
Observe(service->GetDriveFsHost());
}
// Local file system.
file_change_service_observer_.Observe(
FileChangeServiceFactory::GetInstance()->GetService(profile()));
// Volume manager.
auto* const volume_manager = file_manager::VolumeManager::Get(profile());
if (volume_manager)
volume_manager_observer_.Observe(volume_manager);
// Schedule a task to clean up items that belong to volumes that haven't been
// mounted in a reasonable amount of time. The primary goal of handling the
// delayed volume mount is to support volumes that are mounted asynchronously
// during the startup.
clear_non_initialized_items_timer_.Start(
FROM_HERE, base::Minutes(1),
base::BindOnce(&HoldingSpaceFileSystemDelegate::ClearNonInitializedItems,
base::Unretained(this)));
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemsAdded(
const std::vector<const HoldingSpaceItem*>& items) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const bool arc_file_system_disconnected =
IsArcFileSystemDisconnected(profile());
for (const HoldingSpaceItem* item : items) {
if (item->IsInitialized() && item->progress().IsComplete()) {
// Watch the directory containing `item`'s backing file. If the directory
// is already being watched, this will no-op. Note that it is not
// necessary to register a watch if the `item` is in-progress since
// in-progress items are not subject to validity checks.
AddWatchForParent(item->file().file_path);
continue;
}
// In-progress items are not subject to validity checks.
if (!item->progress().IsComplete())
continue;
// If the item has not yet been initialized, check whether it's path can be
// resolved to a file system URL - failure to do so may indicate that the
// volume to which it path belongs is not mounted there. If the file system
// URL cannot be resolved, leave the item in partially initialized state.
// Validity will be checked when the associated mount point is mounted.
// NOTE: Items will not be kept in partially initialized state indefinitely
// - see `clear_non_initialized_items_timer_`.
// NOTE: This does not work well for removable devices, as all removable
// devices have the same top level "external" mount point (media/removable),
// so a removable device path will be successfully resolved even if the
// device is not currently mounted. The logic will have to be updated if
// support for restoring items across removable device mounts becomes a
// requirement.
const GURL file_system_url = holding_space_util::ResolveFileSystemUrl(
profile(), item->file().file_path);
if (file_system_url.is_empty())
continue;
// Defer validity checks (and initialization) for android files if the ARC
// file system connection is not yet ready.
if (arc_file_system_disconnected && ItemBackedByAndroidFile(item))
continue;
holding_space_util::ValidityRequirement requirements;
if (item->type() != HoldingSpaceItem::Type::kPinnedFile) {
requirements.must_be_newer_than = kMaxFileAge;
}
ScheduleFilePathValidityCheck({item->file().file_path, requirements});
}
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemsRemoved(
const std::vector<const HoldingSpaceItem*>& items) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const HoldingSpaceItem* item : items)
MaybeRemoveWatch(item->file().file_path.DirName());
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemUpdated(
const HoldingSpaceItem* item,
const HoldingSpaceItemUpdatedFields& updated_fields) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// In-progress items are not subject to validity checks.
if (item->progress().IsComplete())
AddWatchForParent(item->file().file_path);
}
void HoldingSpaceFileSystemDelegate::OnHoldingSpaceItemInitialized(
const HoldingSpaceItem* item) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
AddWatchForParent(item->file().file_path);
}
void HoldingSpaceFileSystemDelegate::OnVolumeMounted(
MountError error_code,
const file_manager::Volume& volume) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
holding_space_util::FilePathsWithValidityRequirements
file_paths_with_requirements;
// Check validity of partially initialized items under the volume's mount
// path.
for (auto& item : model()->items()) {
if (item->IsInitialized())
continue;
if (!volume.mount_path().IsParent(item->file().file_path)) {
continue;
}
holding_space_util::ValidityRequirement requirements;
if (item->type() != HoldingSpaceItem::Type::kPinnedFile) {
requirements.must_be_newer_than = kMaxFileAge;
}
ScheduleFilePathValidityCheck({item->file().file_path, requirements});
}
}
void HoldingSpaceFileSystemDelegate::OnVolumeUnmounted(
MountError error_code,
const file_manager::Volume& volume) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Drive FS may restart from time to time, so only remove items if drive is
// disabled.
if (volume.type() == file_manager::VOLUME_TYPE_GOOGLE_DRIVE) {
const auto* drive_integration_service =
drive::DriveIntegrationServiceFactory::FindForProfile(profile());
if (drive_integration_service && drive_integration_service->is_enabled()) {
return;
}
}
// Schedule task to remove items under the unmounted file path from the model.
// During suspend, some volumes get unmounted - for example, drive FS. The
// file system delegate gets shutdown to avoid removing items from unmounted
// volumes, but depending on the order in which observers are added to power
// manager dbus client, the file system delegate may get shutdown after
// unmounting a volume. To avoid observer ordering issues, schedule
// asynchronous task to remove unmounted items from the model.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&HoldingSpaceFileSystemDelegate::RemoveItemsParentedByPath,
weak_factory_.GetWeakPtr(), volume.mount_path()));
}
void HoldingSpaceFileSystemDelegate::OnFileCreatedFromShowSaveFilePicker(
const GURL& file_picker_binding_context,
const storage::FileSystemURL& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
holding_space_metrics::RecordFileCreatedFromShowSaveFilePicker(
file_picker_binding_context, url.path());
if (file_picker_binding_context.DomainIs("photoshop.adobe.com")) {
service()->AddItemOfType(HoldingSpaceItem::Type::kPhotoshopWeb, url.path());
}
}
void HoldingSpaceFileSystemDelegate::OnFileModified(
const storage::FileSystemURL& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
OnFilePathModified(url.path());
}
void HoldingSpaceFileSystemDelegate::OnFileMoved(
const storage::FileSystemURL& src,
const storage::FileSystemURL& dst) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
OnFilePathMoved(src.path(), dst.path());
}
void HoldingSpaceFileSystemDelegate::OnFilePathChanged(
const base::FilePath& file_path,
bool error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!error);
// The `file_path` that changed is a directory containing backing files for
// one or more holding space items. Changes to this directory may indicate
// that some, all, or none of these backing files have been removed. Verify
// the existence of these backing files to trigger removal of any holding
// space items that no longer exist.
for (const auto& item : model()->items()) {
if (file_path.IsParent(item->file().file_path)) {
ScheduleFilePathValidityCheck(
{item->file().file_path, /*requirements=*/{}});
}
}
}
void HoldingSpaceFileSystemDelegate::OnFilePathModified(
const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
model()->InvalidateItemImageIf(base::BindRepeating(
[](const base::FilePath& file_path, const HoldingSpaceItem* item) {
return item->file().file_path == file_path;
},
file_path));
}
void HoldingSpaceFileSystemDelegate::OnFilePathMoved(
const base::FilePath& src,
const base::FilePath& dst) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Collect items that should be moved to a new path. This includes:
// * Items whose path matches the source path.
// * Items parented by the source path.
// Maps item ID to the item's new file path.
std::vector<std::pair<std::string, base::FilePath>> items_to_move;
for (auto& item : model()->items()) {
if (src == item->file().file_path) {
items_to_move.push_back(std::make_pair(item->id(), dst));
continue;
}
if (src.IsParent(item->file().file_path)) {
base::FilePath target_path(dst);
if (!src.AppendRelativePath(item->file().file_path, &target_path)) {
NOTREACHED_IN_MIGRATION();
continue;
}
items_to_move.push_back(std::make_pair(item->id(), target_path));
}
}
// Handle existing holding space items under the target file path.
// Moving an existing item to a new path may create conflict within the
// holding space if an item with the target path already exists within the
// holding space. If this is the case, assume that the original item was
// overwritten, and remove it from the holding space.
// NOTE: Don't remove items at destination if no holding space items have to
// be updated due to the file move. The reason for this is to:
// * Support use case where apps change files by moving temp file with
// modifications to the file path.
// * Handle duplicate file path move notifications.
// Instead, update the items as if the target path was modified.
if (items_to_move.empty()) {
OnFilePathModified(dst);
return;
}
// Get a list of the enabled Trash locations. Trash can be enabled and
// disabled via policy, so ensure the latest list is retrieved.
file_manager::trash::TrashPathsMap enabled_trash_locations =
file_manager::trash::GenerateEnabledTrashLocationsForProfile(
profile(), /*base_path=*/base::FilePath());
// Mark items that were moved to an enabled Trash location for removal.
std::set<std::string> item_ids_to_remove;
for (const auto& it : enabled_trash_locations) {
const base::FilePath& trash_location =
it.first.Append(it.second.relative_folder_path);
for (const auto& [id, file_path] : items_to_move) {
if (trash_location.IsParent(file_path))
item_ids_to_remove.insert(id);
}
}
// Mark conflicts with existing items that arise from the move for removal.
for (auto& item : model()->items()) {
if (dst == item->file().file_path || dst.IsParent(item->file().file_path)) {
item_ids_to_remove.insert(item->id());
}
}
// Remove items which have been marked for removal.
model()->RemoveItems(item_ids_to_remove);
// Finally, update the files that have been moved.
for (const auto& [id, file_path] : items_to_move) {
if (item_ids_to_remove.count(id))
continue;
// File.
const GURL file_system_url =
holding_space_util::ResolveFileSystemUrl(profile(), file_path);
const HoldingSpaceFile::FileSystemType file_system_type =
holding_space_util::ResolveFileSystemType(profile(), file_system_url);
// Update.
model()->UpdateItem(id)->SetBackingFile(
HoldingSpaceFile(file_path, file_system_type, file_system_url));
}
// If a backing file update occurred, it's possible that there are no longer
// any holding space items associated with `src`. When that is the case, `src`
// no longer needs to be watched.
MaybeRemoveWatch(src);
}
void HoldingSpaceFileSystemDelegate::ScheduleFilePathValidityCheck(
holding_space_util::FilePathWithValidityRequirement requirement) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
pending_file_path_validity_checks_.push_back(std::move(requirement));
if (file_path_validity_checks_scheduled_)
return;
file_path_validity_checks_scheduled_ = true;
// Schedule file validity check for pending items. The check is scheduled
// asynchronously so path checks added in quick succession are handled in a
// single batch.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&HoldingSpaceFileSystemDelegate::RunPendingFilePathValidityChecks,
weak_factory_.GetWeakPtr()));
}
void HoldingSpaceFileSystemDelegate::RunPendingFilePathValidityChecks() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
holding_space_util::FilePathsWithValidityRequirements requirements;
requirements.swap(pending_file_path_validity_checks_);
file_path_validity_checks_scheduled_ = false;
holding_space_util::PartitionFilePathsByValidity(
profile(), std::move(requirements),
base::BindOnce(
&HoldingSpaceFileSystemDelegate::OnFilePathValidityChecksComplete,
weak_factory_.GetWeakPtr()));
}
void HoldingSpaceFileSystemDelegate::OnFilePathValidityChecksComplete(
std::vector<base::FilePath> valid_paths,
std::vector<base::FilePath> invalid_paths) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const bool arc_file_system_disconnected =
IsArcFileSystemDisconnected(profile());
// Remove items with invalid paths.
model()->RemoveIf(base::BindRepeating(
[](bool arc_file_system_disconnected,
const std::vector<base::FilePath>* invalid_paths,
const HoldingSpaceItem* item) {
// In-progress items are not subject to validity checks.
if (!item->progress().IsComplete())
return false;
// Avoid removing Android files if connection to ARC file system has
// been lost (e.g. Android container might have crashed). Validity
// checks will be re-run once the file system gets connected.
if (arc_file_system_disconnected && ItemBackedByAndroidFile(item))
return false;
return base::Contains(*invalid_paths, item->file().file_path);
},
arc_file_system_disconnected, &invalid_paths));
std::vector<const HoldingSpaceItem*> items_to_initialize;
for (auto& item : model()->items()) {
// Defer initialization of items backed by android files if the connection
// to ARC file system service has been lost.
if (arc_file_system_disconnected && ItemBackedByAndroidFile(item.get()))
continue;
if (!item->IsInitialized() &&
base::Contains(valid_paths, item->file().file_path)) {
items_to_initialize.push_back(item.get());
}
}
for (auto* item : items_to_initialize) {
const base::FilePath& file_path = item->file().file_path;
const GURL file_system_url =
holding_space_util::ResolveFileSystemUrl(profile(), file_path);
const HoldingSpaceFile::FileSystemType file_system_type =
holding_space_util::ResolveFileSystemType(profile(), file_system_url);
model()->InitializeOrRemoveItem(
item->id(),
HoldingSpaceFile(file_path, file_system_type, file_system_url));
}
}
void HoldingSpaceFileSystemDelegate::AddWatchForParent(
const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
file_system_watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileSystemWatcher::AddWatchForParent,
file_system_watcher_->GetWeakPtr(), file_path));
}
void HoldingSpaceFileSystemDelegate::MaybeRemoveWatch(
const base::FilePath& file_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// The watch for `file_path` should only be removed if no holding space items
// exist in the model which are backed by files it directly parents.
const bool remove_watch =
base::ranges::none_of(model()->items(), [&file_path](const auto& item) {
return item->IsInitialized() &&
item->file().file_path.DirName() == file_path;
});
if (!remove_watch)
return;
file_system_watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&FileSystemWatcher::RemoveWatch,
file_system_watcher_->GetWeakPtr(), file_path));
}
void HoldingSpaceFileSystemDelegate::RemoveItemsParentedByPath(
const base::FilePath& parent_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
model()->RemoveIf(base::BindRepeating(
[](const base::FilePath& parent_path, const HoldingSpaceItem* item) {
return parent_path.IsParent(item->file().file_path);
},
parent_path));
}
void HoldingSpaceFileSystemDelegate::ClearNonInitializedItems() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
model()->RemoveIf(base::BindRepeating(
[](Profile* profile, const HoldingSpaceItem* item) {
if (item->IsInitialized())
return false;
// Do not remove items whose path can be resolved to a file system URL.
// In this case, the associated mount point has been mounted, but the
// initialization may have been delayed - for example due to
// issues/delays with initializing ARC.
const GURL url = holding_space_util::ResolveFileSystemUrl(
profile, item->file().file_path);
return url.is_empty();
},
profile()));
}
} // namespace ash