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