// Copyright 2013 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_manager/file_watcher.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/guest_os_file_watcher.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/common/task_util.h"
using content::BrowserThread;
namespace file_manager {
namespace {
// Creates a base::FilePathWatcher and starts watching at |watch_path| with
// |callback|. Returns NULL on failure.
base::FilePathWatcher* CreateAndStartFilePathWatcher(
const base::FilePath& watch_path,
const base::FilePathWatcher::Callback& callback) {
DCHECK(!callback.is_null());
std::unique_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
if (!watcher->Watch(watch_path, base::FilePathWatcher::Type::kNonRecursive,
callback)) {
return nullptr;
}
return watcher.release();
}
std::unique_ptr<guest_os::GuestOsFileWatcher> GetForPath(
Profile* profile,
const base::FilePath& local_path) {
// TODO(b/217469540): The default Crostini mount isn't using mount providers
// yet so check for it explicitly and handle it differently.
base::FilePath crostini_mount = util::GetCrostiniMountDirectory(profile);
base::FilePath relative_path;
if (local_path == crostini_mount ||
crostini_mount.AppendRelativePath(local_path, &relative_path)) {
return std::make_unique<guest_os::GuestOsFileWatcher>(
ash::ProfileHelper::GetUserIdHashFromProfile(profile),
crostini::DefaultContainerId(), std::move(crostini_mount),
std::move(relative_path));
}
auto* service = guest_os::GuestOsService::GetForProfile(profile);
if (!service) {
return nullptr;
}
auto* registry = service->MountProviderRegistry();
for (const auto id : registry->List()) {
auto* provider = registry->Get(id);
base::FilePath mount_path = util::GetGuestOsMountDirectory(
util::GetGuestOsMountPointName(profile, provider->GuestId()));
if (local_path == mount_path ||
mount_path.AppendRelativePath(local_path, &relative_path)) {
return provider->CreateFileWatcher(std::move(mount_path),
std::move(relative_path));
}
}
return nullptr;
}
} // namespace
FileWatcher::FileWatcher(const base::FilePath& virtual_path)
: sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
virtual_path_(virtual_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
FileWatcher::~FileWatcher() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
sequenced_task_runner_->DeleteSoon(FROM_HERE, local_file_watcher_.get());
}
void FileWatcher::AddListener(const url::Origin& listener) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
origins_[listener]++;
}
void FileWatcher::RemoveListener(const url::Origin& listener) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = origins_.find(listener);
if (it == origins_.end()) {
LOG(ERROR) << " Listener [" << listener
<< "] tries to unsubscribe from virtual path ["
<< virtual_path_.value() << "] it isn't subscribed";
return;
}
// If entry found - decrease it's count and remove if necessary
--it->second;
if (it->second == 0) {
origins_.erase(it);
}
}
std::vector<url::Origin> FileWatcher::GetListeners() const {
std::vector<url::Origin> origins;
for (const auto& kv : origins_) {
origins.push_back(kv.first);
}
return origins;
}
void FileWatcher::WatchLocalFile(
Profile* profile,
const base::FilePath& local_path,
const base::FilePathWatcher::Callback& file_watcher_callback,
BoolCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback.is_null());
DCHECK(!local_file_watcher_);
// If this is a crostini SSHFS path, use CrostiniFileWatcher.
crostini_file_watcher_ = GetForPath(profile, local_path);
if (crostini_file_watcher_) {
crostini_file_watcher_->Watch(std::move(file_watcher_callback),
std::move(callback));
return;
}
sequenced_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&CreateAndStartFilePathWatcher, local_path,
google_apis::CreateRelayCallback(file_watcher_callback)),
base::BindOnce(&FileWatcher::OnWatcherStarted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void FileWatcher::OnWatcherStarted(BoolCallback callback,
base::FilePathWatcher* file_watcher) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback.is_null());
DCHECK(!local_file_watcher_);
if (file_watcher) {
local_file_watcher_ = file_watcher;
std::move(callback).Run(true);
} else {
std::move(callback).Run(false);
}
}
} // namespace file_manager