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