filesystem.cpp (10300B)
1 #include "filesystem.h" 2 3 #include <array> 4 #include <iostream> 5 6 #ifndef _WIN32 7 // filesystem is only available on macOS Catalina 10.15+ 8 // filesystem causes linker errors in gcc-8 if linked statically 9 #define USE_DIRENT 10 #include <cstdlib> 11 #include <cstring> 12 #include <pwd.h> 13 #endif 14 15 #ifdef USE_DIRENT 16 #include <dirent.h> 17 #include <unistd.h> 18 #include <sys/stat.h> 19 #else 20 #include <filesystem> 21 #endif 22 23 #ifdef _WIN32 24 #define NOMINMAX 25 #define NOSERVICE 26 #include <Windows.h> 27 #include <shlobj_core.h> 28 #else 29 #include <dlfcn.h> 30 #endif 31 32 #ifdef _MSC_VER 33 #include <cfloat> 34 #elif defined(HAVE_SSE) 35 #include <immintrin.h> 36 #endif 37 38 #ifdef __APPLE__ 39 #include <sys/types.h> 40 #include <sys/sysctl.h> 41 #endif 42 43 namespace baseLib::filesystem 44 { 45 #ifdef _WIN32 46 constexpr char g_nativePathSeparator = '\\'; 47 #else 48 constexpr char g_nativePathSeparator = '/'; 49 #endif 50 constexpr char g_otherPathSeparator = g_nativePathSeparator == '\\' ? '/' : '\\'; 51 52 std::string getCurrentDirectory() 53 { 54 #ifdef USE_DIRENT 55 char temp[1024]; 56 getcwd(temp, sizeof(temp)); 57 return temp; 58 #else 59 return std::filesystem::current_path().string(); 60 #endif 61 } 62 63 bool createDirectory(const std::string& _dir) 64 { 65 #ifdef USE_DIRENT 66 constexpr auto dirAttribs = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; 67 for(size_t i=0; i<_dir.size(); ++i) 68 { 69 if(_dir[i] == '/' || _dir[i] == '\\') 70 { 71 const auto d = _dir.substr(0,i); 72 mkdir(d.c_str(), dirAttribs); 73 } 74 } 75 return mkdir(_dir.c_str(), dirAttribs) == 0; 76 #else 77 return std::filesystem::create_directories(_dir); 78 #endif 79 } 80 81 std::string validatePath(std::string _path) 82 { 83 if(_path.empty()) 84 return _path; 85 86 for (char& ch : _path) 87 { 88 if(ch == g_otherPathSeparator) 89 ch = g_nativePathSeparator; 90 } 91 92 if(_path.back() == g_nativePathSeparator) 93 return _path; 94 95 _path += g_nativePathSeparator; 96 return _path; 97 } 98 99 bool getDirectoryEntries(std::vector<std::string>& _files, const std::string& _folder) 100 { 101 #ifdef USE_DIRENT 102 DIR *dir; 103 struct dirent *ent; 104 if ((dir = opendir(_folder.c_str()))) 105 { 106 while ((ent = readdir(dir))) 107 { 108 std::string f = ent->d_name; 109 110 if(f == "." || f == "..") 111 continue; 112 113 std::string file = _folder; 114 115 if(file.back() != '/' && file.back() != '\\') 116 file += '/'; 117 118 file += f; 119 120 _files.push_back(file); 121 } 122 closedir(dir); 123 } 124 else 125 { 126 // LOG("Failed to open directory " << _folder << ", error " << errno); 127 std::cerr << "Failed to open directory " << _folder << ", error " << errno << '\n'; 128 return false; 129 } 130 #else 131 try 132 { 133 const auto u8Path = std::filesystem::u8path(_folder); 134 for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(u8Path)) 135 { 136 const auto &file = entry.path(); 137 138 try 139 { 140 _files.push_back(file.u8string()); 141 } 142 catch(std::exception& e) 143 { 144 //LOG(e.what()); 145 std::cerr << e.what() << '\n'; 146 } 147 } 148 } 149 catch (std::exception& e) 150 { 151 //LOG(e.what()); 152 std::cerr << e.what() << '\n'; 153 return false; 154 } 155 #endif 156 return !_files.empty(); 157 } 158 159 bool findFiles(std::vector<std::string>& _files, const std::string& _rootPath, const std::string& _extension, const size_t _minSize, const size_t _maxSize) 160 { 161 std::vector<std::string> files; 162 163 getDirectoryEntries(files, _rootPath); 164 165 for (const auto& file : files) 166 { 167 if(!hasExtension(file, _extension)) 168 continue; 169 170 if (!_minSize && !_maxSize) 171 { 172 _files.push_back(file); 173 continue; 174 } 175 176 const auto size = getFileSize(file); 177 178 if (_minSize && size < _minSize) 179 continue; 180 if (_maxSize && size > _maxSize) 181 continue; 182 183 _files.push_back(file); 184 } 185 return !_files.empty(); 186 } 187 188 std::string findFile(const std::string& _rootPath, const std::string& _extension, const size_t _minSize, const size_t _maxSize) 189 { 190 std::vector<std::string> files; 191 if(!findFiles(files, _rootPath, _extension, _minSize, _maxSize)) 192 return {}; 193 return files.front(); 194 } 195 196 197 std::string lowercase(const std::string &_src) 198 { 199 std::string str(_src); 200 for (char& i : str) 201 i = static_cast<char>(tolower(i)); 202 return str; 203 } 204 205 std::string getExtension(const std::string &_name) 206 { 207 const auto pos = _name.find_last_of('.'); 208 if (pos != std::string::npos) 209 return _name.substr(pos); 210 return {}; 211 } 212 213 std::string stripExtension(const std::string& _name) 214 { 215 const auto pos = _name.find_last_of('.'); 216 if (pos != std::string::npos) 217 return _name.substr(0, pos); 218 return _name; 219 } 220 221 std::string getFilenameWithoutPath(const std::string& _name) 222 { 223 const auto pos = _name.find_last_of("/\\"); 224 if (pos != std::string::npos) 225 return _name.substr(pos + 1); 226 return _name; 227 } 228 229 std::string getPath(const std::string& _filename) 230 { 231 const auto pos = _filename.find_last_of("/\\"); 232 if (pos != std::string::npos) 233 return _filename.substr(0, pos); 234 return _filename; 235 } 236 237 size_t getFileSize(const std::string& _file) 238 { 239 FILE* hFile = openFile(_file, "rb"); 240 if (!hFile) 241 return 0; 242 243 fseek(hFile, 0, SEEK_END); 244 const auto size = static_cast<size_t>(ftell(hFile)); 245 fclose(hFile); 246 return size; 247 } 248 249 bool isDirectory(const std::string& _path) 250 { 251 #ifdef USE_DIRENT 252 struct stat statbuf; 253 stat(_path.c_str(), &statbuf); 254 if (S_ISDIR(statbuf.st_mode)) 255 return true; 256 return false; 257 #else 258 return std::filesystem::is_directory(_path); 259 #endif 260 } 261 bool hasExtension(const std::string& _filename, const std::string& _extension) 262 { 263 if (_extension.empty()) 264 return true; 265 266 return lowercase(getExtension(_filename)) == lowercase(_extension); 267 } 268 269 bool writeFile(const std::string& _filename, const std::vector<uint8_t>& _data) 270 { 271 return writeFile(_filename, _data.data(), _data.size()); 272 } 273 274 bool writeFile(const std::string& _filename, const uint8_t* _data, size_t _size) 275 { 276 auto* hFile = openFile(_filename, "wb"); 277 if(!hFile) 278 return false; 279 const auto written = fwrite(&_data[0], 1, _size, hFile); 280 fclose(hFile); 281 return written == _size; 282 } 283 284 bool readFile(std::vector<uint8_t>& _data, const std::string& _filename) 285 { 286 auto* hFile = openFile(_filename, "rb"); 287 if(!hFile) 288 return false; 289 290 fseek(hFile, 0, SEEK_END); 291 const auto size = ftell(hFile); 292 fseek(hFile, 0, SEEK_SET); 293 294 if(!size) 295 { 296 fclose(hFile); 297 _data.clear(); 298 return true; 299 } 300 301 if(_data.size() != static_cast<size_t>(size)) 302 _data.resize(size); 303 304 const auto read = fread(_data.data(), 1, _data.size(), hFile); 305 fclose(hFile); 306 return read == _data.size(); 307 } 308 309 FILE* openFile(const std::string& _name, const char* _mode) 310 { 311 #ifdef _WIN32 312 // convert filename 313 std::wstring nameW; 314 nameW.resize(_name.size()); 315 const int newSize = MultiByteToWideChar(CP_UTF8, 0, _name.c_str(), static_cast<int>(_name.size()), const_cast<wchar_t *>(nameW.c_str()), static_cast<int>(_name.size())); 316 nameW.resize(newSize); 317 318 // convert mode 319 wchar_t mode[32]{0}; 320 MultiByteToWideChar(CP_UTF8, 0, _mode, static_cast<int>(strlen(_mode)), mode, (int)std::size(mode)); 321 return _wfopen(nameW.c_str(), mode); 322 #else 323 return fopen(_name.c_str(), _mode); 324 #endif 325 } 326 327 std::string getHomeDirectory() 328 { 329 #ifdef _WIN32 330 std::array<char, MAX_PATH<<1> data; 331 if (SHGetSpecialFolderPathA (nullptr, data.data(), CSIDL_PROFILE, FALSE)) 332 return validatePath(data.data()); 333 334 const auto* home = getenv("USERPROFILE"); 335 if (home) 336 return home; 337 338 const auto* drive = getenv("HOMEDRIVE"); 339 const auto* path = getenv("HOMEPATH"); 340 341 if (drive && path) 342 return std::string(drive) + std::string(path); 343 344 return "C:\\Users\\Default"; // meh, what can we do? 345 #else 346 const char* home = getenv("HOME"); 347 if (home && strlen(home) > 0) 348 return home; 349 const auto* pw = getpwuid(getuid()); 350 if(pw) 351 return std::string(pw->pw_dir); 352 return "/tmp"; // better ideas welcome 353 #endif 354 } 355 356 std::string getSpecialFolderPath(const SpecialFolderType _type) 357 { 358 #ifdef _WIN32 359 std::array<char, MAX_PATH<<1> path; 360 361 int csidl; 362 switch (_type) 363 { 364 case SpecialFolderType::UserDocuments: 365 csidl = CSIDL_PERSONAL; 366 break; 367 case SpecialFolderType::PrivateAppData: 368 csidl = CSIDL_APPDATA; 369 break; 370 default: 371 return {}; 372 } 373 if (SHGetSpecialFolderPathA (nullptr, path.data(), csidl, FALSE)) 374 return validatePath(path.data()); 375 #else 376 const auto h = std::getenv("HOME"); 377 const std::string home = validatePath(getHomeDirectory()); 378 379 #if defined(__APPLE__) 380 switch (_type) 381 { 382 case SpecialFolderType::UserDocuments: 383 return home + "Documents/"; 384 case SpecialFolderType::PrivateAppData: 385 return home + "Library/Application Support/"; 386 default: 387 return {}; 388 } 389 #else 390 // https://specifications.freedesktop.org/basedir-spec/latest/ 391 switch (_type) 392 { 393 case SpecialFolderType::UserDocuments: 394 { 395 const auto* docDir = std::getenv("XDG_DATA_HOME"); 396 if(docDir && strlen(docDir) > 0) 397 return validatePath(docDir); 398 return home + ".local/share/"; 399 } 400 case SpecialFolderType::PrivateAppData: 401 { 402 const auto* confDir = std::getenv("XDG_CONFIG_HOME"); 403 if(confDir && strlen(confDir) > 0) 404 return validatePath(confDir); 405 return home + ".config/"; 406 } 407 default: 408 return {}; 409 } 410 #endif 411 #endif 412 return {}; 413 } 414 }