1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "io/dev/file_monitor.h"
17 
18 #include <cstddef>
19 
20 #include <base/containers/iterator.h>
21 #include <base/containers/string.h>
22 #include <base/containers/string_view.h>
23 #include <base/containers/type_traits.h>
24 #include <base/containers/unique_ptr.h>
25 #include <base/containers/unordered_map.h>
26 #include <base/containers/vector.h>
27 #include <base/namespace.h>
28 #include <core/io/intf_directory.h>
29 #include <core/io/intf_file_manager.h>
30 #include <core/namespace.h>
31 #include <core/perf/cpu_perf_scope.h>
32 
33 CORE_BEGIN_NAMESPACE()
34 using BASE_NS::string;
35 using BASE_NS::string_view;
36 using BASE_NS::vector;
37 
RecursivelyCollectAllFiles(string & path)38 void FileMonitor::RecursivelyCollectAllFiles(string& path)
39 {
40     auto dir = fileManager_.OpenDirectory(path);
41     if (dir == nullptr) {
42         // path does not exist.
43         return;
44     }
45     const auto& entries = dir->GetEntries();
46     const size_t oldLength = path.length();
47     for (const IDirectory::Entry& entry : entries) {
48         if (entry.name == "." || entry.name == "..") {
49             continue;
50         }
51 
52         path.reserve(oldLength + entry.name.length() + 1);
53         path += entry.name;
54 
55         if (entry.type == IDirectory::Entry::DIRECTORY) {
56             path += '/';
57         }
58 
59         auto iterator = files_.find(path);
60         if (iterator != files_.end()) {
61             // File or directory being tracked, see if it is modified.
62             if (entry.timestamp == iterator->second.timestamp) {
63                 iterator->second.state = FileInfo::NOCHANGE;
64             } else {
65                 iterator->second.timestamp = entry.timestamp;
66                 iterator->second.state = FileInfo::MODIFIED;
67             }
68         } else {
69             // This is a new file or directory, start tracking it.
70             files_.insert({ path, { entry.timestamp, FileInfo::ADDED } });
71         }
72         if (entry.type == IDirectory::Entry::DIRECTORY) {
73             RecursivelyCollectAllFiles(path);
74         }
75         path.resize(oldLength);
76     }
77 }
FileMonitor(IFileManager & manager)78 FileMonitor::FileMonitor(IFileManager& manager) : fileManager_(manager) {}
CleanPath(const string_view inPath,string & path)79 void FileMonitor::CleanPath(const string_view inPath, string& path)
80 {
81     // cleanup slashes. (partial sanitation, path needs to end with '/' and slashes must be '/' (not '\\')
82     if ((inPath.back() != '/') && (inPath.back() != '\\')) {
83         path.reserve(inPath.size() + 1);
84         path = inPath;
85         path += '/';
86     } else {
87         path = inPath;
88     }
89     for (auto& c : path) {
90         if (c == '\\') {
91             c = '/';
92         }
93     }
94 }
95 
AddPath(const string_view inPath)96 bool FileMonitor::AddPath(const string_view inPath)
97 {
98     string path;
99     CleanPath(inPath, path);
100     if (IsWatchingDirectory(path) || IsWatchingSubDirectory(path)) {
101         // Already exists or unable to resolve.
102         return false;
103     }
104     // Collect information for files in path.
105     RecursivelyCollectAllFiles(path);
106     if (path.capacity() > pathTmp_.capacity()) {
107         pathTmp_.reserve(path.capacity());
108     }
109     // Update state.
110     for (auto& ref : files_) {
111         ref.second.state = FileInfo::REMOVED;
112     }
113     // Store directory to watch list.
114     directories_.push_back(path);
115     return true;
116 }
117 
RemovePath(const string_view inPath)118 bool FileMonitor::RemovePath(const string_view inPath)
119 {
120     string path;
121     CleanPath(inPath, path);
122     auto iterator = directories_.cbegin();
123     for (; iterator != directories_.cend(); ++iterator) {
124         if (*iterator == path) {
125             // scan through tracked files, and remove the ones that start with "path"
126             for (auto it = files_.begin(); it != files_.end();) {
127                 const auto pos = it->first.find(path);
128                 if (pos == 0) {
129                     it = files_.erase(it);
130                 } else {
131                     ++it;
132                 }
133             }
134             // Remove directory from watch list.
135             directories_.erase(iterator);
136             return true;
137         }
138     }
139     return false;
140 }
141 
IsWatchingDirectory(const string_view inPath)142 bool FileMonitor::IsWatchingDirectory(const string_view inPath)
143 {
144     string path;
145     CleanPath(inPath, path);
146     for (const auto& ref : directories_) {
147         if (path.find(ref) != string_view::npos) {
148             // Already watching this directory or it's parent.
149             return true;
150         }
151     }
152     return false;
153 }
154 
IsWatchingSubDirectory(const string_view inPath)155 bool FileMonitor::IsWatchingSubDirectory(const string_view inPath)
156 {
157     string path;
158     CleanPath(inPath, path);
159     for (const auto& ref : directories_) {
160         if (ref.find(path) != string_view::npos) {
161             // Already watching subdirectory of given directory.
162             return true;
163         }
164     }
165     return false;
166 }
167 
ScanModifications(vector<string> & added,vector<string> & removed,vector<string> & modified)168 void FileMonitor::ScanModifications(vector<string>& added, vector<string>& removed, vector<string>& modified)
169 {
170     CORE_CPU_PERF_SCOPE("Other", "FileMonitor", "ScanModifications()");
171     // Collect all files that are under monitoring.
172     for (const auto& ref : directories_) {
173         pathTmp_ = ref;
174         RecursivelyCollectAllFiles(pathTmp_);
175     }
176 
177     // See which of the files are removed.
178     for (auto it = files_.begin(); it != files_.end();) {
179         if (it->second.state == FileInfo::REMOVED) {
180             removed.push_back(it->first);
181         } else if (it->second.state == FileInfo::MODIFIED) {
182             modified.push_back(it->first);
183         } else if (it->second.state == FileInfo::ADDED) {
184             added.push_back(it->first);
185         }
186         if (it->second.state != FileInfo::REMOVED) {
187             // default state is removed.
188             it->second.state = FileInfo::REMOVED;
189             ++it;
190         } else {
191             it = files_.erase(it);
192         }
193     }
194 }
195 
GetMonitoredFiles() const196 vector<string> FileMonitor::GetMonitoredFiles() const
197 {
198     vector<string> filesRes;
199     filesRes.reserve(files_.size());
200     for (auto& f : files_) {
201         filesRes.push_back(f.first);
202     }
203     return filesRes;
204 }
205 
206 CORE_END_NAMESPACE()
207