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