1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/file_manager/file_watcher.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/task/post_task.h"
12 #include "base/task/thread_pool.h"
13 #include "base/task_runner_util.h"
14 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
15 #include "chrome/browser/chromeos/crostini/crostini_util.h"
16 #include "chrome/browser/chromeos/file_manager/path_util.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "google_apis/drive/task_util.h"
19
20 using content::BrowserThread;
21
22 namespace file_manager {
23 namespace {
24
25 // Creates a base::FilePathWatcher and starts watching at |watch_path| with
26 // |callback|. Returns NULL on failure.
CreateAndStartFilePathWatcher(const base::FilePath & watch_path,const base::FilePathWatcher::Callback & callback)27 base::FilePathWatcher* CreateAndStartFilePathWatcher(
28 const base::FilePath& watch_path,
29 const base::FilePathWatcher::Callback& callback) {
30 DCHECK(!callback.is_null());
31
32 std::unique_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
33 if (!watcher->Watch(watch_path, false /* recursive */, callback))
34 return nullptr;
35
36 return watcher.release();
37 }
38
39 } // namespace
40
41 class FileWatcher::CrostiniFileWatcher
42 : public crostini::CrostiniFileChangeObserver {
43 public:
GetForPath(Profile * profile,const base::FilePath & local_path)44 static std::unique_ptr<CrostiniFileWatcher> GetForPath(
45 Profile* profile,
46 const base::FilePath& local_path) {
47 base::FilePath crostini_mount = util::GetCrostiniMountDirectory(profile);
48 base::FilePath crostini_path;
49 if (local_path == crostini_mount ||
50 crostini_mount.AppendRelativePath(local_path, &crostini_path)) {
51 crostini::CrostiniManager* crostini_manager =
52 crostini::CrostiniManager::GetForProfile(profile);
53 if (crostini_manager) {
54 return std::make_unique<CrostiniFileWatcher>(crostini_manager,
55 std::move(crostini_mount),
56 std::move(crostini_path));
57 }
58 }
59 return nullptr;
60 }
61
CrostiniFileWatcher(crostini::CrostiniManager * crostini_manager,base::FilePath crostini_mount,base::FilePath crostini_path)62 CrostiniFileWatcher(crostini::CrostiniManager* crostini_manager,
63 base::FilePath crostini_mount,
64 base::FilePath crostini_path)
65 : crostini_manager_(crostini_manager),
66 crostini_mount_(std::move(crostini_mount)),
67 crostini_path_(std::move(crostini_path)),
68 container_id_(crostini::ContainerId::GetDefault()) {}
69
~CrostiniFileWatcher()70 ~CrostiniFileWatcher() override {
71 if (file_watcher_callback_) {
72 crostini_manager_->RemoveFileChangeObserver(this);
73 crostini_manager_->RemoveFileWatch(container_id_, crostini_path_);
74 }
75 }
76
Watch(base::FilePathWatcher::Callback file_watcher_callback,FileWatcher::BoolCallback callback)77 void Watch(base::FilePathWatcher::Callback file_watcher_callback,
78 FileWatcher::BoolCallback callback) {
79 DCHECK(!file_watcher_callback_);
80 file_watcher_callback_ = std::move(file_watcher_callback);
81 crostini_manager_->AddFileChangeObserver(this);
82 crostini_manager_->AddFileWatch(container_id_, crostini_path_,
83 std::move(callback));
84 }
85
86 private:
87 // crostini::CrostiniFileChangeObserver overrides
OnCrostiniFileChanged(const crostini::ContainerId & container_id,const base::FilePath & path)88 void OnCrostiniFileChanged(const crostini::ContainerId& container_id,
89 const base::FilePath& path) override {
90 DCHECK_CURRENTLY_ON(BrowserThread::UI);
91 if (container_id != container_id_) {
92 return;
93 }
94
95 DCHECK(file_watcher_callback_);
96 file_watcher_callback_.Run(crostini_mount_.Append(path), /*error=*/false);
97 }
98
99 crostini::CrostiniManager* crostini_manager_;
100 const base::FilePath crostini_mount_;
101 const base::FilePath crostini_path_;
102 const crostini::ContainerId container_id_;
103 base::FilePathWatcher::Callback file_watcher_callback_;
104 };
105
FileWatcher(const base::FilePath & virtual_path)106 FileWatcher::FileWatcher(const base::FilePath& virtual_path)
107 : sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
108 {base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
109 local_file_watcher_(nullptr),
110 virtual_path_(virtual_path) {
111 DCHECK_CURRENTLY_ON(BrowserThread::UI);
112 }
113
~FileWatcher()114 FileWatcher::~FileWatcher() {
115 DCHECK_CURRENTLY_ON(BrowserThread::UI);
116
117 sequenced_task_runner_->DeleteSoon(FROM_HERE, local_file_watcher_);
118 }
119
AddExtension(const std::string & extension_id)120 void FileWatcher::AddExtension(const std::string& extension_id) {
121 DCHECK_CURRENTLY_ON(BrowserThread::UI);
122
123 extensions_[extension_id]++;
124 }
125
RemoveExtension(const std::string & extension_id)126 void FileWatcher::RemoveExtension(const std::string& extension_id) {
127 DCHECK_CURRENTLY_ON(BrowserThread::UI);
128
129 ExtensionCountMap::iterator it = extensions_.find(extension_id);
130 if (it == extensions_.end()) {
131 LOG(ERROR) << " Extension [" << extension_id
132 << "] tries to unsubscribe from folder ["
133 << virtual_path_.value()
134 << "] it isn't subscribed";
135 return;
136 }
137
138 // If entry found - decrease it's count and remove if necessary
139 --it->second;
140 if (it->second == 0)
141 extensions_.erase(it);
142 }
143
GetExtensionIds() const144 std::vector<std::string> FileWatcher::GetExtensionIds() const {
145 std::vector<std::string> extension_ids;
146 for (ExtensionCountMap::const_iterator iter = extensions_.begin();
147 iter != extensions_.end(); ++iter) {
148 extension_ids.push_back(iter->first);
149 }
150 return extension_ids;
151 }
152
WatchLocalFile(Profile * profile,const base::FilePath & local_path,const base::FilePathWatcher::Callback & file_watcher_callback,BoolCallback callback)153 void FileWatcher::WatchLocalFile(
154 Profile* profile,
155 const base::FilePath& local_path,
156 const base::FilePathWatcher::Callback& file_watcher_callback,
157 BoolCallback callback) {
158 DCHECK_CURRENTLY_ON(BrowserThread::UI);
159 DCHECK(!callback.is_null());
160 DCHECK(!local_file_watcher_);
161
162 // If this is a crostini SSHFS path, use CrostiniFileWatcher.
163 crostini_file_watcher_ = CrostiniFileWatcher::GetForPath(profile, local_path);
164 if (crostini_file_watcher_) {
165 crostini_file_watcher_->Watch(std::move(file_watcher_callback),
166 std::move(callback));
167 return;
168 }
169
170 base::PostTaskAndReplyWithResult(
171 sequenced_task_runner_.get(), FROM_HERE,
172 base::BindOnce(&CreateAndStartFilePathWatcher, local_path,
173 google_apis::CreateRelayCallback(file_watcher_callback)),
174 base::BindOnce(&FileWatcher::OnWatcherStarted,
175 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
176 }
177
OnWatcherStarted(BoolCallback callback,base::FilePathWatcher * file_watcher)178 void FileWatcher::OnWatcherStarted(BoolCallback callback,
179 base::FilePathWatcher* file_watcher) {
180 DCHECK_CURRENTLY_ON(BrowserThread::UI);
181 DCHECK(!callback.is_null());
182 DCHECK(!local_file_watcher_);
183
184 if (file_watcher) {
185 local_file_watcher_ = file_watcher;
186 std::move(callback).Run(true);
187 } else {
188 std::move(callback).Run(false);
189 }
190 }
191
192 } // namespace file_manager
193