• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/std_directory.h"
17 
18 #include <algorithm>
19 
20 // MINGW has filesystem but it's buggy: https://sourceforge.net/p/mingw-w64/bugs/737/
21 // Not using it for now.
22 #ifdef _MSC_VER
23 #include <filesystem>
24 #define HAS_FILESYSTEM
25 #else
26 #include <dirent.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #endif
30 
31 #include <core/log.h>
32 #include <core/namespace.h>
33 
34 CORE_BEGIN_NAMESPACE()
35 using BASE_NS::make_unique;
36 using BASE_NS::string;
37 using BASE_NS::string_view;
38 using BASE_NS::vector;
39 
40 #ifdef HAS_FILESYSTEM
41 
42 // Hiding the directory implementation from the header.
43 struct DirImpl {
DirImplDirImpl44     DirImpl(const string_view path) : path_(path) {}
45     string path_;
46 };
47 
48 namespace {
GetTimeStamp(const std::filesystem::directory_entry & entry)49 uint64_t GetTimeStamp(const std::filesystem::directory_entry& entry)
50 {
51     return static_cast<uint64_t>(entry.last_write_time().time_since_epoch().count());
52 }
53 
GetEntryType(const std::filesystem::directory_entry & entry)54 IDirectory::Entry::Type GetEntryType(const std::filesystem::directory_entry& entry)
55 {
56     if (entry.is_directory()) {
57         return IDirectory::Entry::DIRECTORY;
58     } else if (entry.is_regular_file()) {
59         return IDirectory::Entry::FILE;
60     }
61 
62     return IDirectory::Entry::UNKNOWN;
63 }
64 } // namespace
65 
66 StdDirectory::StdDirectory() = default;
67 
~StdDirectory()68 StdDirectory::~StdDirectory()
69 {
70     Close();
71 }
72 
Close()73 void StdDirectory::Close()
74 {
75     if (dir_) {
76         dir_.reset();
77     }
78 }
79 
Open(const string_view path)80 bool StdDirectory::Open(const string_view path)
81 {
82     CORE_ASSERT_MSG((dir_ == nullptr), "Dir already opened.");
83     std::error_code ec;
84     if (std::filesystem::is_directory({ path.begin().ptr(), path.end().ptr() }, ec)) {
85         dir_ = make_unique<DirImpl>(path);
86         return true;
87     }
88     return false;
89 }
90 
GetEntries() const91 vector<IDirectory::Entry> StdDirectory::GetEntries() const
92 {
93     CORE_ASSERT_MSG(dir_, "Dir not open");
94     vector<IDirectory::Entry> result;
95     if (dir_) {
96         std::error_code ec;
97         for (auto& iter :
98             std::filesystem::directory_iterator({ dir_->path_.begin().ptr(), dir_->path_.end().ptr() }, ec)) {
99             const auto filename = iter.path().filename().u8string();
100             auto str = string(filename.c_str(), filename.length());
101             result.emplace_back(IDirectory::Entry { GetEntryType(iter), move(str), GetTimeStamp(iter) });
102         }
103     }
104 
105     return result;
106 }
107 
108 #else // HAS_FILESYSTEM not defined
109 
110 // Hiding the directory implementation from the header.
111 struct DirImpl {
DirImplDirImpl112     DirImpl(const string_view path, DIR* aDir) : path_(path), dir_(aDir) {}
113     string path_;
114     DIR* dir_;
115 };
116 
117 namespace {
CreateEntry(const string_view path,const dirent & entry)118 IDirectory::Entry CreateEntry(const string_view path, const dirent& entry)
119 {
120     const auto fullPath = path + string(entry.d_name);
121     struct stat statBuf {};
122     const auto statResult = (stat(fullPath.c_str(), &statBuf) == 0);
123 
124     IDirectory::Entry::Type type;
125 #ifdef _DIRENT_HAVE_D_TYPE
126     switch (entry.d_type) {
127         case DT_DIR:
128             type = IDirectory::Entry::DIRECTORY;
129             break;
130         case DT_REG:
131             type = IDirectory::Entry::FILE;
132             break;
133         default:
134             type = IDirectory::Entry::UNKNOWN;
135             break;
136     }
137 #else
138     type = IDirectory::Entry::UNKNOWN;
139     if (statResult) {
140         if (S_ISDIR(statBuf.st_mode)) {
141             type = IDirectory::Entry::DIRECTORY;
142         } else if (S_ISREG(statBuf.st_mode)) {
143             type = IDirectory::Entry::FILE;
144         }
145     }
146 #endif
147 
148     // Get the timestamp for the directory entry.
149     uint64_t timestamp = 0;
150     if (statResult) {
151         timestamp = statBuf.st_mtime;
152     }
153 
154     return IDirectory::Entry { type, entry.d_name, timestamp };
155 }
156 } // namespace
157 
StdDirectory()158 StdDirectory::StdDirectory() : dir_() {}
159 
~StdDirectory()160 StdDirectory::~StdDirectory()
161 {
162     Close();
163 }
164 
Close()165 void StdDirectory::Close()
166 {
167     if (dir_) {
168         closedir(dir_->dir_);
169         dir_.reset();
170     }
171 }
172 
Open(const string_view path)173 bool StdDirectory::Open(const string_view path)
174 {
175     CORE_ASSERT_MSG((dir_ == nullptr), "Dir already opened.");
176     DIR* dir = opendir(string(path).c_str());
177     if (dir) {
178         dir_ = make_unique<DirImpl>(path, dir);
179         return true;
180     }
181     return false;
182 }
183 
GetEntries() const184 vector<IDirectory::Entry> StdDirectory::GetEntries() const
185 {
186     CORE_ASSERT_MSG(dir_, "Dir not open");
187     vector<IDirectory::Entry> result;
188     if (dir_) {
189         // Go back to start.
190         rewinddir(dir_->dir_);
191 
192         // Iterate all entries and write to result.
193         struct dirent* directoryEntry;
194         while ((directoryEntry = readdir(dir_->dir_)) != nullptr) {
195             if (!strcmp(directoryEntry->d_name, ".") || !strcmp(directoryEntry->d_name, "..")) {
196                 continue;
197             }
198             result.emplace_back(CreateEntry(dir_->path_, *directoryEntry));
199         }
200     }
201 
202     return result;
203 }
204 
205 #endif // HAS_FILESYSTEM
206 
ResolveAbsolutePath(const string_view pathIn,bool isDirectory)207 string StdDirectory::ResolveAbsolutePath(const string_view pathIn, bool isDirectory)
208 {
209     string absolutePath;
210     auto path = pathIn;
211 #ifdef _WIN32
212     // remove the '/' slash..
213     path = path.substr(1);
214 #endif
215 
216 #ifdef HAS_FILESYSTEM
217     std::error_code ec;
218     std::filesystem::path fsPath = std::filesystem::canonical({ path.begin().ptr(), path.end().ptr() }, ec);
219     if (ec.value() == 0) {
220         const auto pathStr = fsPath.string();
221         absolutePath.assign(pathStr.data(), pathStr.size());
222     }
223 #elif defined(_WIN32)
224     char resolvedPath[_MAX_PATH] = {};
225     if (_fullpath(resolvedPath, string(path).c_str(), _MAX_PATH) != nullptr) {
226         if (isDirectory) {
227             auto handle = opendir(resolvedPath);
228             if (handle) {
229                 closedir(handle);
230                 absolutePath = resolvedPath;
231             }
232         } else {
233             auto handle = fopen(resolvedPath, "r");
234             if (handle) {
235                 fclose(handle);
236                 absolutePath = resolvedPath;
237             }
238         }
239     }
240 #elif defined(__linux__)
241     char resolvedPath[PATH_MAX];
242     if (realpath(string(path).c_str(), resolvedPath) != nullptr) {
243         absolutePath = resolvedPath;
244     }
245 #endif
246 
247     FormatPath(absolutePath, isDirectory);
248 
249     return absolutePath;
250 }
251 
FormatPath(string & path,bool isDirectory)252 void StdDirectory::FormatPath(string& path, bool isDirectory)
253 {
254     const size_t length = path.length();
255 
256     // Make directory separators consistent.
257     std::replace(path.begin(), path.end(), '\\', '/');
258 
259     // Ensure there is last separator in place.
260     if (path.length() > 0 && isDirectory) {
261         if (path[length - 1] != '/') {
262             path += '/';
263         }
264     }
265 }
266 
GetDirName(const string_view fullPath)267 string StdDirectory::GetDirName(const string_view fullPath)
268 {
269     auto path = string(fullPath);
270     StdDirectory::FormatPath(path, false);
271 
272     const size_t separatorPos = path.find_last_of('/');
273     if (separatorPos == string::npos) {
274         return ".";
275     } else {
276         path.resize(separatorPos);
277         return path;
278     }
279 }
280 
GetBaseName(const string_view fullPath)281 string StdDirectory::GetBaseName(const string_view fullPath)
282 {
283     auto path = string(fullPath);
284     StdDirectory::FormatPath(path, false);
285 
286     const size_t separatorPos = path.find_last_of('/');
287     if (separatorPos == string::npos) {
288         return path;
289     } else {
290         path.erase(0, separatorPos + 1);
291         return path;
292     }
293 }
294 CORE_END_NAMESPACE()
295