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