• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/FileMonitor.h"
17 
18 #include <algorithm>
19 #include <iostream>
20 #include <optional>
21 
22 #ifdef _WIN32
23 #if __has_include(<filesystem>)
24 #include <filesystem>
25 #define HAS_FILESYSTEM 1
26 #endif
27 #define USE_WIN32
28 #include <Windows.h>
29 #else
30 #include <dirent.h>
31 #endif
32 
33 #include <climits>
34 #include <sys/stat.h>
35 
36 namespace ige {
37 namespace {
FormatPath(std::string & path,bool isDirectory)38 void FormatPath(std::string& path, bool isDirectory)
39 {
40     size_t length = path.length();
41 
42     // Make directory separators consistent.
43     std::replace(path.begin(), path.end(), '\\', '/');
44 
45     // Ensure there is last separator in place.
46     if (path.length() > 0 && isDirectory) {
47         if (path[length - 1] != '/') {
48             path += '/';
49         }
50     }
51 }
52 
ResolveAbsolutePath(std::string_view path,bool isDirectory)53 std::string ResolveAbsolutePath(std::string_view path, bool isDirectory)
54 {
55     std::string absolutePath;
56 
57 #ifdef HAS_FILESYSTEM
58     std::error_code error;
59     if (auto resolvedPath = std::filesystem::canonical(std::filesystem::u8path(path), error); !error) {
60         absolutePath = resolvedPath.u8string();
61     }
62 #elif defined(USE_WIN32)
63     char resolvedPath[_MAX_PATH] = {};
64     auto ret = GetFullPathNameA(path.data(), sizeof(resolvedPath), resolvedPath, nullptr);
65     if (ret < sizeof(resolvedPath)) {
66         WIN32_FIND_DATAA data;
67         HANDLE handle = FindFirstFileA(resolvedPath, &data);
68         if (handle != INVALID_HANDLE_VALUE) {
69             absolutePath = resolvedPath;
70             FindClose(handle);
71         }
72     }
73 #elif defined(__PLATFORM_MW_32__) || defined(__PLATFORM_MW_64__)
74     char resolvedPath[_MAX_PATH] = {};
75     if (_fullpath(resolvedPath, path.data(), _MAX_PATH) != nullptr) {
76         if (isDirectory) {
77             auto handle = opendir(resolvedPath);
78             if (handle) {
79                 closedir(handle);
80                 absolutePath = resolvedPath;
81             }
82         } else {
83             auto handle = fopen(resolvedPath, "r");
84             if (handle) {
85                 fclose(handle);
86                 absolutePath = resolvedPath;
87             }
88         }
89     }
90 #else
91     char resolvedPath[PATH_MAX];
92     if (realpath(path.data(), resolvedPath) != nullptr) {
93         absolutePath = resolvedPath;
94     }
95 #endif
96 
97     FormatPath(absolutePath, isDirectory);
98 
99     return absolutePath;
100 }
101 
102 struct Collection {
103     std::vector<std::string> files;
104     std::vector<std::string> directories;
105 };
106 
GatherFilesAndDirectories(std::string_view path)107 std::optional<Collection> GatherFilesAndDirectories(std::string_view path)
108 {
109     Collection collection;
110 #ifdef HAS_FILESYSTEM
111     std::error_code error;
112     for (auto const& entry : std::filesystem::directory_iterator { std::filesystem::u8path(path), error }) {
113         if (entry.is_directory()) {
114             collection.directories.push_back(entry.path().filename().u8string());
115         } else if (entry.is_regular_file()) {
116             collection.files.push_back(entry.path().filename().u8string());
117         }
118     }
119     if (error) {
120         return std::nullopt;
121     }
122 #elif defined(USE_WIN32)
123     WIN32_FIND_DATAA data;
124     HANDLE handle = INVALID_HANDLE_VALUE;
125     handle = FindFirstFileA((std::string(path) + "*.*").c_str(), &data);
126     if (handle == INVALID_HANDLE_VALUE) {
127         // Unable to open directory.
128         return std::nullopt;
129     }
130     do {
131         if (data.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) {
132             // skip devices.
133         } else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
134             collection.directories.push_back(data.cFileName);
135         } else {
136             collection.files.push_back(data.cFileName);
137         }
138     } while (FindNextFileA(handle, &data));
139 
140     FindClose(handle);
141 #else
142     DIR* handle = opendir(path.data());
143     if (!handle) {
144         // Unable to open directory.
145         return std::nullopt;
146     }
147 
148     struct dirent* directory;
149     while ((directory = readdir(handle)) != nullptr) {
150         switch (directory->d_type) {
151             case DT_DIR:
152                 collection.directories.push_back(directory->d_name);
153                 break;
154 
155             case DT_REG:
156                 collection.files.push_back(directory->d_name);
157                 break;
158 
159             default:
160                 break;
161         }
162     }
163 
164     closedir(handle);
165 #endif
166     return collection;
167 }
168 
RecursivelyCollectAllFiles(std::string_view path,std::vector<std::string> & aFiles)169 void RecursivelyCollectAllFiles(std::string_view path, std::vector<std::string>& aFiles)
170 {
171     auto filesAndDirs = GatherFilesAndDirectories(path);
172     if (!filesAndDirs) {
173         return;
174     }
175     // Collect files.
176     std::string originalPath;
177     for (const auto& filename : filesAndDirs->files) {
178         originalPath = path;
179         originalPath += filename;
180         std::string absoluteFilename = ResolveAbsolutePath(originalPath, false);
181         if (!absoluteFilename.empty()) {
182             aFiles.push_back(absoluteFilename);
183         }
184     }
185 
186     // Process recursively.
187     for (const auto& directoryName : filesAndDirs->directories) {
188         if (directoryName != "." && directoryName != "..") {
189             originalPath = path;
190             originalPath += directoryName;
191             std::string absoluteDirectory = ResolveAbsolutePath(originalPath, true);
192             if (!absoluteDirectory.empty()) {
193                 RecursivelyCollectAllFiles(absoluteDirectory, aFiles);
194             }
195         }
196     }
197 }
198 } // namespace
199 
AddPath(std::string_view path)200 bool FileMonitor::AddPath(std::string_view path)
201 {
202     std::string absolutePath = ResolveAbsolutePath(path, true);
203     if (absolutePath.empty() || IsWatchingDirectory(absolutePath) || IsWatchingSubDirectory(absolutePath)) {
204         // Already exists or unable to resolve.
205         return false;
206     }
207 
208     std::vector<std::string> files;
209     RecursivelyCollectAllFiles(absolutePath, files);
210 
211     // Add all files to watch list.
212     for (const auto& ref : files) {
213         AddFile(ref);
214     }
215 
216     // Store directory to watch list.
217     mDirectories.push_back(std::move(absolutePath));
218 
219     return true;
220 }
221 
RemovePath(std::string_view path)222 bool FileMonitor::RemovePath(std::string_view path)
223 {
224     std::string absolutePath = ResolveAbsolutePath(path, true);
225 
226     std::vector<std::string>::iterator iterator = std::find(mDirectories.begin(), mDirectories.end(), absolutePath);
227     if (iterator != mDirectories.end()) {
228         // Collect all tracked files within this directory.
229         std::vector<std::string> files;
230         RecursivelyCollectAllFiles(absolutePath, files);
231 
232         // Stop tracking of removed files.
233         for (const auto& ref : files) {
234             // Remove from tracked list.
235             RemoveFile(ref);
236         }
237 
238         // Remove directory from watch list.
239         mDirectories.erase(iterator);
240         return true;
241     }
242 
243     return false;
244 }
245 
AddFile(std::string_view path)246 bool FileMonitor::AddFile(std::string_view path)
247 {
248     std::string absolutePath = ResolveAbsolutePath(path, false);
249     if (absolutePath.empty() || mFiles.find(absolutePath) != mFiles.end()) {
250         // Already exists or unable to resolve.
251         return false;
252     }
253 #if HAS_FILESYSTEM
254     std::error_code error;
255     const auto time = std::filesystem::last_write_time(std::filesystem::u8path(absolutePath), error);
256     if (error) {
257         return false;
258     }
259     // Collect file data.
260     mFiles[absolutePath].timestamp = time.time_since_epoch().count();
261 #else
262     struct stat ds;
263     if (stat(absolutePath.c_str(), &ds) != 0) {
264         // Unable to get file stats.
265         return false;
266     }
267 
268     // Collect file data.
269     FileInfo info;
270     info.timestamp = ds.st_mtime;
271     mFiles[absolutePath] = info;
272 #endif
273     return true;
274 }
275 
RemoveFile(const std::string & path)276 bool FileMonitor::RemoveFile(const std::string& path)
277 {
278     auto iterator = mFiles.find(path);
279     if (iterator != mFiles.end()) {
280         mFiles.erase(iterator);
281         return true;
282     }
283 
284     return false;
285 }
286 
IsWatchingDirectory(std::string_view path)287 bool FileMonitor::IsWatchingDirectory(std::string_view path)
288 {
289     for (const auto& ref : mDirectories) {
290         if (path.find(ref) != std::string::npos) {
291             // Already watching this directory or it's parent.
292             return true;
293         }
294     }
295 
296     return false;
297 }
298 
IsWatchingSubDirectory(std::string_view path)299 bool FileMonitor::IsWatchingSubDirectory(std::string_view path)
300 {
301     for (const auto& ref : mDirectories) {
302         if (ref.find(path) != std::string::npos) {
303             // Already watching subdirectory of given directory.
304             return true;
305         }
306     }
307 
308     return false;
309 }
310 
ScanModifications(std::vector<std::string> & aAdded,std::vector<std::string> & aRemoved,std::vector<std::string> & aModified)311 void FileMonitor::ScanModifications(
312     std::vector<std::string>& aAdded, std::vector<std::string>& aRemoved, std::vector<std::string>& aModified)
313 {
314     // Collect all files that are under monitoring.
315     std::vector<std::string> files;
316     for (const auto& ref : mDirectories) {
317         RecursivelyCollectAllFiles(ref, files);
318     }
319 
320     // See which of the files are modified.
321     for (const auto& file : files) {
322         auto iterator = mFiles.find(file);
323         if (iterator != mFiles.end()) {
324 #if HAS_FILESYSTEM
325             const auto u8path = std::filesystem::u8path(file);
326             std::error_code error;
327             const auto time = std::filesystem::last_write_time(u8path, error).time_since_epoch().count();
328             if ((!error) && (time != iterator->second.timestamp)) {
329                 // This file is modified.
330                 aModified.push_back(file);
331 
332                 // Store new time.
333                 iterator->second.timestamp = time;
334             }
335 #else
336             // File being watched, see if it is modified.
337             struct stat fs;
338             if ((stat(file.c_str(), &fs) == 0) && (fs.st_mtime != iterator->second.timestamp)) {
339                 // This file is modified.
340                 aModified.push_back(file);
341 
342                 // Store new time.
343                 iterator->second.timestamp = fs.st_mtime;
344             }
345 #endif
346         } else {
347             // This is a new file.
348             aAdded.push_back(file);
349         }
350     }
351 
352     // See which of the files are removed.
353     for (const auto& ref : mFiles) {
354         if (std::find(files.begin(), files.end(), ref.first) == files.end()) {
355             // This file no longer exists.
356             aRemoved.push_back(ref.first);
357         }
358     }
359 
360     // Stop tracking of removed files.
361     for (const auto& ref : aRemoved) {
362         // Remove from tracked list.
363         RemoveFile(ref);
364     }
365 
366     // Start tracking of new files.
367     for (const auto& ref : aAdded) {
368         // Add to tracking list.
369         AddFile(ref);
370     }
371 }
372 
GetMonitoredFiles() const373 std::vector<std::string> FileMonitor::GetMonitoredFiles() const
374 {
375     std::vector<std::string> files;
376     files.reserve(mFiles.size());
377 
378     for (auto it = mFiles.begin(), end = mFiles.end(); it != end; ++it) {
379         files.push_back(it->first);
380     }
381     return files;
382 }
383 } // namespace ige
384