| // Copyright 2013 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/ash/file_manager/file_watcher.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/task/post_task.h" |
| #include "base/task/task_runner_util_forward.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/ash/crostini/crostini_manager.h" |
| #include "chrome/browser/ash/crostini/crostini_util.h" |
| #include "chrome/browser/ash/file_manager/path_util.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(); |
| } |
| |
| } // namespace |
| |
| class FileWatcher::CrostiniFileWatcher |
| : public crostini::CrostiniFileChangeObserver { |
| public: |
| static std::unique_ptr<CrostiniFileWatcher> GetForPath( |
| Profile* profile, |
| const base::FilePath& local_path) { |
| base::FilePath crostini_mount = util::GetCrostiniMountDirectory(profile); |
| base::FilePath crostini_path; |
| if (local_path == crostini_mount || |
| crostini_mount.AppendRelativePath(local_path, &crostini_path)) { |
| crostini::CrostiniManager* crostini_manager = |
| crostini::CrostiniManager::GetForProfile(profile); |
| if (crostini_manager) { |
| return std::make_unique<CrostiniFileWatcher>(crostini_manager, |
| std::move(crostini_mount), |
| std::move(crostini_path)); |
| } |
| } |
| return nullptr; |
| } |
| |
| CrostiniFileWatcher(crostini::CrostiniManager* crostini_manager, |
| base::FilePath crostini_mount, |
| base::FilePath crostini_path) |
| : crostini_manager_(crostini_manager), |
| crostini_mount_(std::move(crostini_mount)), |
| crostini_path_(std::move(crostini_path)), |
| container_id_(crostini::ContainerId::GetDefault()) {} |
| |
| ~CrostiniFileWatcher() override { |
| if (file_watcher_callback_) { |
| crostini_manager_->RemoveFileChangeObserver(this); |
| crostini_manager_->RemoveFileWatch(container_id_, crostini_path_); |
| } |
| } |
| |
| void Watch(base::FilePathWatcher::Callback file_watcher_callback, |
| FileWatcher::BoolCallback callback) { |
| DCHECK(!file_watcher_callback_); |
| file_watcher_callback_ = std::move(file_watcher_callback); |
| crostini_manager_->AddFileChangeObserver(this); |
| crostini_manager_->AddFileWatch(container_id_, crostini_path_, |
| std::move(callback)); |
| } |
| |
| private: |
| // crostini::CrostiniFileChangeObserver overrides |
| void OnCrostiniFileChanged(const crostini::ContainerId& container_id, |
| const base::FilePath& path) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (container_id != container_id_) { |
| return; |
| } |
| |
| DCHECK(file_watcher_callback_); |
| file_watcher_callback_.Run(crostini_mount_.Append(path), /*error=*/false); |
| } |
| |
| crostini::CrostiniManager* crostini_manager_; |
| const base::FilePath crostini_mount_; |
| const base::FilePath crostini_path_; |
| const crostini::ContainerId container_id_; |
| base::FilePathWatcher::Callback file_watcher_callback_; |
| }; |
| |
| FileWatcher::FileWatcher(const base::FilePath& virtual_path) |
| : sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE})), |
| local_file_watcher_(nullptr), |
| 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_); |
| } |
| |
| 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_ = CrostiniFileWatcher::GetForPath(profile, local_path); |
| if (crostini_file_watcher_) { |
| crostini_file_watcher_->Watch(std::move(file_watcher_callback), |
| std::move(callback)); |
| return; |
| } |
| |
| base::PostTaskAndReplyWithResult( |
| sequenced_task_runner_.get(), 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 |