File.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. /*
  2. * Author: Patrick-Christopher Mattulat
  3. * Co-Author: Claude Sonnet 4.6 (LLM)
  4. * Company: Lynar Studios
  5. * E-Mail: webmaster@lynarstudios.com
  6. * Created: 2020-08-15
  7. * Changed: 2026-06-23
  8. *
  9. * */
  10. #include <algorithm>
  11. #include <cstdio>
  12. #ifdef _WIN32
  13. #include <direct.h>
  14. #endif
  15. #include <fstream>
  16. #include <list>
  17. #include <ls-std/core/exception/FileOperationException.hpp>
  18. #include <ls-std/io/File.hpp>
  19. #include <ls-std/io/FilePathSeparator.hpp>
  20. #include <ls-std/io/FilePathSeparatorMatch.hpp>
  21. #include <numeric>
  22. #include <sstream>
  23. #include <sys/stat.h>
  24. #ifdef _WIN32
  25. #include <tchar.h>
  26. #endif
  27. #if defined(unix) || defined(__APPLE__)
  28. #include <unistd.h>
  29. #endif
  30. #include <vector>
  31. using ls::standard::core::Class;
  32. using ls::standard::core::FileOperationException;
  33. using ls::standard::io::File;
  34. using ls::standard::io::FilePathSeparator;
  35. using ls::standard::io::FilePathSeparatorMatch;
  36. using std::accumulate;
  37. using std::find_if;
  38. using std::getline;
  39. using std::ifstream;
  40. using std::ios;
  41. using std::list;
  42. using std::move;
  43. using std::ofstream;
  44. using std::remove;
  45. using std::remove_if;
  46. using std::rename;
  47. using std::replace;
  48. using std::streamoff;
  49. using std::streampos;
  50. using std::string;
  51. using std::stringstream;
  52. using std::vector;
  53. File::File(string _absoluteFilePath) : Class("File"), absoluteFilePath(_normalizePath(::move(_absoluteFilePath)))
  54. {}
  55. File::~File() noexcept = default;
  56. bool File::operator==(const File &_file) const
  57. {
  58. return _equals(*this, _file);
  59. }
  60. bool File::operator!=(const File &_file) const
  61. {
  62. return !_equals(*this, _file);
  63. }
  64. bool File::canExecute() const
  65. {
  66. return _isExecutable(this->absoluteFilePath);
  67. }
  68. bool File::canRead() const
  69. {
  70. bool readable{};
  71. #if defined(unix) || defined(__APPLE__)
  72. readable = _isReadableUnix(this->absoluteFilePath);
  73. #endif
  74. #ifdef _WIN32
  75. readable = File::_isReadableWindows(this->absoluteFilePath);
  76. #endif
  77. return readable;
  78. }
  79. bool File::canWrite() const
  80. {
  81. return _isWritable(this->absoluteFilePath);
  82. }
  83. void File::createNewFile() const
  84. {
  85. if (!_exists(this->absoluteFilePath))
  86. {
  87. ofstream file{this->absoluteFilePath};
  88. file.close();
  89. }
  90. else
  91. {
  92. throw FileOperationException{R"lit(file ")lit" + this->absoluteFilePath + R"lit(" could not be created!)lit"};
  93. }
  94. }
  95. bool File::exists() const
  96. {
  97. return _exists(this->absoluteFilePath);
  98. }
  99. string File::getAbsoluteFilePath() const
  100. {
  101. return this->absoluteFilePath;
  102. }
  103. string File::getName() const
  104. {
  105. string copy = this->absoluteFilePath;
  106. // if it's a directory, remove separator from end, if it does exist
  107. if (_isDirectory(this->absoluteFilePath))
  108. {
  109. copy.erase(remove_if(copy.end() - 1, copy.end(), FilePathSeparatorMatch()), copy.end());
  110. }
  111. // now get the file / directory name
  112. const auto base = find_if(copy.rbegin(), copy.rend(), FilePathSeparatorMatch()).base();
  113. return string{base, copy.end()};
  114. }
  115. string File::getParent() const
  116. {
  117. return _getParent(this->absoluteFilePath);
  118. }
  119. string File::getWorkingDirectory()
  120. {
  121. string workingDirectory{};
  122. #if defined(unix) || defined(__APPLE__)
  123. workingDirectory = _getWorkingDirectoryUnix();
  124. #endif
  125. #ifdef _WIN32
  126. workingDirectory = File::_getWorkingDirectoryWindows();
  127. #endif
  128. return workingDirectory;
  129. }
  130. size_t File::getSize() const
  131. {
  132. streampos fileSize{};
  133. if (_exists(this->absoluteFilePath))
  134. {
  135. ifstream fileHandler{this->absoluteFilePath, ios::in};
  136. fileSize = fileHandler.tellg();
  137. fileHandler.seekg(0, ios::end);
  138. fileSize = fileHandler.tellg() - fileSize;
  139. fileHandler.close();
  140. }
  141. const auto off = static_cast<streamoff>(fileSize);
  142. return static_cast<size_t>(off); // not redundant ;)
  143. }
  144. bool File::isDirectory() const
  145. {
  146. return _isDirectory(this->absoluteFilePath);
  147. }
  148. bool File::isFile() const
  149. {
  150. return _isFile(this->absoluteFilePath);
  151. }
  152. time_t File::lastModified() const
  153. {
  154. return _lastModified(this->absoluteFilePath);
  155. }
  156. ::list<string> File::list() const
  157. {
  158. ::list<string> fileList{};
  159. if (_isDirectory(this->absoluteFilePath))
  160. {
  161. fileList = _list(this->absoluteFilePath);
  162. }
  163. return fileList;
  164. }
  165. ::list<string> File::listFiles() const
  166. {
  167. ::list<string> fileList{};
  168. if (_isDirectory(this->absoluteFilePath))
  169. {
  170. fileList = _listFiles(this->absoluteFilePath);
  171. }
  172. return fileList;
  173. }
  174. void File::makeDirectory() const
  175. {
  176. if (!_makeDirectory(this->absoluteFilePath))
  177. {
  178. throw FileOperationException{R"lit(directory ")lit" + this->absoluteFilePath + R"lit(" could not be created!)lit"};
  179. }
  180. }
  181. void File::makeDirectories() const
  182. {
  183. const vector<string> subDirectories = _splitIntoSubDirectoryNames(this->absoluteFilePath);
  184. const char separator = FilePathSeparator::get();
  185. string currentHierarchy{};
  186. for (const auto &subDirectory : subDirectories)
  187. {
  188. currentHierarchy += subDirectory;
  189. if (!_exists(currentHierarchy + separator) && !currentHierarchy.empty() && !_makeDirectory(currentHierarchy))
  190. {
  191. throw FileOperationException{"operation: create directory"};
  192. }
  193. currentHierarchy += separator;
  194. }
  195. }
  196. void File::remove() const
  197. {
  198. if (_isFile(this->absoluteFilePath))
  199. {
  200. ::remove(this->absoluteFilePath.c_str());
  201. }
  202. if (_isDirectory(this->absoluteFilePath))
  203. {
  204. _remove(this->absoluteFilePath);
  205. }
  206. }
  207. bool File::renameTo(const string &_newName)
  208. {
  209. const bool renamed = _renameTo(this->absoluteFilePath, _newName);
  210. if (renamed)
  211. {
  212. this->absoluteFilePath = _newName;
  213. }
  214. return renamed;
  215. }
  216. void File::reset(const string &_newPath)
  217. {
  218. this->absoluteFilePath = _normalizePath(_newPath);
  219. }
  220. #ifdef _WIN32
  221. void File::_addToFileListWindows(const string &_path, bool _withDirectories, WIN32_FIND_DATA _data, ::list<string> &_list)
  222. {
  223. const char separator = FilePathSeparator::get();
  224. string absolutePath = _path + separator + _data.cFileName;
  225. if (_withDirectories)
  226. {
  227. _list.emplace_back(absolutePath);
  228. }
  229. else
  230. {
  231. if (File::_isFile(absolutePath))
  232. {
  233. _list.emplace_back(absolutePath);
  234. }
  235. }
  236. }
  237. #endif
  238. #if defined(unix) || defined(__APPLE__)
  239. void File::_addToFileListUnix(const string &_path, const bool _withDirectories, const dirent *directoryEntity, ::list<string> &_list)
  240. {
  241. const char separator = FilePathSeparator::get();
  242. string absolutePath = _path + separator + directoryEntity->d_name;
  243. if (_withDirectories)
  244. {
  245. _list.emplace_back(absolutePath);
  246. }
  247. else
  248. {
  249. if (_isFile(absolutePath))
  250. {
  251. _list.emplace_back(absolutePath);
  252. }
  253. }
  254. }
  255. #endif
  256. bool File::_equals(const File &_file, const File &_foreignFile)
  257. {
  258. bool isEqual = _file.getAbsoluteFilePath() == _foreignFile.getAbsoluteFilePath();
  259. if (_file.exists() && _foreignFile.exists())
  260. {
  261. isEqual = isEqual && _file.canRead() == _foreignFile.canRead();
  262. isEqual = isEqual && _file.canWrite() == _foreignFile.canWrite();
  263. isEqual = isEqual && _file.canExecute() == _foreignFile.canExecute();
  264. }
  265. return isEqual;
  266. }
  267. bool File::_exists(const string &_path)
  268. {
  269. struct stat _stat
  270. {
  271. };
  272. return (stat(_path.c_str(), &_stat) == 0);
  273. }
  274. string File::_getParent(const string &_path)
  275. {
  276. vector<string> subDirectoryNames = File::_splitIntoSubDirectoryNames(_path);
  277. const char separator = FilePathSeparator::get();
  278. subDirectoryNames.pop_back();
  279. return accumulate(subDirectoryNames.begin(), subDirectoryNames.end(), string{}, [separator](string parent, const string &subDirectoryName) { return ::move(parent) + subDirectoryName + separator; });
  280. }
  281. #if defined(unix) || defined(__APPLE__)
  282. string File::_getWorkingDirectoryUnix()
  283. {
  284. string workingDirectory{};
  285. if (string buffer(PATH_MAX, 'x'); getcwd(buffer.data(), buffer.size()) == nullptr)
  286. {
  287. throw FileOperationException{"operation: get working directory"};
  288. }
  289. else
  290. {
  291. workingDirectory = string(buffer);
  292. }
  293. return workingDirectory;
  294. }
  295. #endif
  296. #ifdef _WIN32
  297. string File::_getWorkingDirectoryWindows()
  298. {
  299. string workingDirectory{};
  300. TCHAR buffer[MAX_PATH];
  301. if (!GetCurrentDirectory(MAX_PATH, buffer))
  302. {
  303. throw FileOperationException{"operation: get working directory"};
  304. }
  305. else
  306. {
  307. workingDirectory = string(buffer);
  308. }
  309. return workingDirectory;
  310. }
  311. #endif
  312. bool File::_isDirectory(const string &_path)
  313. {
  314. bool match{};
  315. if (struct stat _stat{}; stat(_path.c_str(), &_stat) == 0)
  316. {
  317. match = _stat.st_mode & static_cast<unsigned short>(S_IFDIR);
  318. }
  319. return match;
  320. }
  321. bool File::_isExecutable(const string &_path)
  322. {
  323. bool executable{};
  324. if (_exists(_path))
  325. {
  326. struct stat _stat
  327. {
  328. };
  329. if (stat(_path.c_str(), &_stat) == 0)
  330. {
  331. executable = (_stat.st_mode & static_cast<unsigned short>(S_IEXEC)) != 0;
  332. }
  333. }
  334. return executable;
  335. }
  336. bool File::_isFile(const string &_path)
  337. {
  338. bool match{};
  339. if (struct stat _stat{}; stat(_path.c_str(), &_stat) == 0)
  340. {
  341. match = _stat.st_mode & static_cast<unsigned>(S_IFREG);
  342. }
  343. return match;
  344. }
  345. #if defined(unix) || defined(__APPLE__)
  346. bool File::_isReadableUnix(const string &_path)
  347. {
  348. bool readable{};
  349. if (_exists(_path))
  350. {
  351. struct stat _stat
  352. {
  353. };
  354. if (stat(_path.c_str(), &_stat) == 0)
  355. {
  356. readable = (_stat.st_mode & static_cast<unsigned>(S_IREAD)) != 0;
  357. }
  358. }
  359. else
  360. {
  361. throw FileOperationException{"operation: fetch permissions"};
  362. }
  363. return readable;
  364. }
  365. #endif
  366. #ifdef _WIN32
  367. bool File::_isReadableWindows(const string &_path)
  368. {
  369. bool readable;
  370. WIN32_FIND_DATA data{};
  371. HANDLE handleFind = FindFirstFile(_path.c_str(), &data);
  372. if (handleFind != INVALID_HANDLE_VALUE)
  373. {
  374. readable = GetFileAttributes(data.cFileName) & (unsigned) FILE_ATTRIBUTE_READONLY;
  375. }
  376. else
  377. {
  378. throw FileOperationException{"operation: fetch permissions"};
  379. }
  380. return readable;
  381. }
  382. #endif
  383. bool File::_isWritable(const string &_path)
  384. {
  385. bool writable{};
  386. if (_exists(_path))
  387. {
  388. struct stat _stat
  389. {
  390. };
  391. if (stat(_path.c_str(), &_stat) == 0)
  392. {
  393. writable = (_stat.st_mode & static_cast<unsigned>(S_IWRITE)) != 0;
  394. }
  395. }
  396. return writable;
  397. }
  398. time_t File::_lastModified(const string &_path)
  399. {
  400. time_t lastModifiedTimeStamp{};
  401. if (struct stat _stat{}; stat(_path.c_str(), &_stat) == 0)
  402. {
  403. lastModifiedTimeStamp = _stat.st_mtime;
  404. }
  405. return lastModifiedTimeStamp;
  406. }
  407. ::list<string> File::_list(const string &_path)
  408. {
  409. ::list<string> filesInDirectory{};
  410. #if defined(unix) || defined(__APPLE__)
  411. filesInDirectory = _listUnix(_path, true);
  412. #endif
  413. #ifdef _WIN32
  414. filesInDirectory = File::_listWindows(_path, true);
  415. #endif
  416. return filesInDirectory;
  417. }
  418. ::list<string> File::_listFiles(const string &_path)
  419. {
  420. ::list<string> filesInDirectory{};
  421. #if defined(unix) || defined(__APPLE__)
  422. filesInDirectory = _listUnix(_path, false);
  423. #endif
  424. #ifdef _WIN32
  425. filesInDirectory = File::_listWindows(_path, false);
  426. #endif
  427. return filesInDirectory;
  428. }
  429. #if defined(unix) || defined(__APPLE__)
  430. ::list<string> File::_listUnix(const string &_path, const bool withDirectories)
  431. {
  432. ::list<string> filesInDirectory{};
  433. DIR *directory = opendir(_path.c_str());
  434. const dirent *directoryEntity;
  435. string absolutePath{};
  436. while ((directoryEntity = readdir(directory)) != nullptr)
  437. {
  438. _addToFileListUnix(_path, withDirectories, directoryEntity, filesInDirectory);
  439. }
  440. closedir(directory);
  441. return filesInDirectory;
  442. }
  443. #endif
  444. #ifdef _WIN32
  445. ::list<string> File::_listWindows(const string &_path, bool withDirectories)
  446. {
  447. ::list<string> filesInDirectory{};
  448. WIN32_FIND_DATA data{};
  449. HANDLE hFind;
  450. string pattern{_path + FilePathSeparator::get() + "*"};
  451. if ((hFind = FindFirstFile(pattern.c_str(), &data)) != INVALID_HANDLE_VALUE)
  452. {
  453. do
  454. {
  455. File::_addToFileListWindows(_path, withDirectories, data, filesInDirectory);
  456. } while (FindNextFile(hFind, &data) != 0);
  457. FindClose(hFind);
  458. }
  459. return filesInDirectory;
  460. }
  461. #endif
  462. bool File::_makeDirectory(const string &_path)
  463. {
  464. int result{};
  465. #ifdef _WIN32
  466. result = _mkdir(_path.c_str());
  467. #endif
  468. #if defined(unix) || defined(__APPLE__)
  469. result = mkdir(_path.c_str(), 0777);
  470. #endif
  471. return result == 0;
  472. }
  473. string File::_normalizePath(string _path)
  474. {
  475. _path = _replaceWrongSeparator(_path);
  476. _path = _reduceSeparators(_path);
  477. return _path;
  478. }
  479. string File::_reduceSeparators(const string &_path)
  480. {
  481. static const char separator = {FilePathSeparator::get()};
  482. string normalizedPath{};
  483. size_t index{};
  484. while (index < _path.size())
  485. {
  486. if (_path[index] == separator)
  487. {
  488. normalizedPath += _path[index];
  489. do
  490. {
  491. index++;
  492. } while (_path[index] == separator);
  493. }
  494. else
  495. {
  496. normalizedPath += _path[index];
  497. index++;
  498. }
  499. }
  500. return normalizedPath;
  501. }
  502. void File::_remove(const string &_path)
  503. {
  504. #if defined(unix) || defined(__APPLE__)
  505. _removeUnix(_path);
  506. #endif
  507. #ifdef _WIN32
  508. File::_removeWindows(_path);
  509. #endif
  510. }
  511. #if defined(unix) || defined(__APPLE__)
  512. void File::_removeUnix(const string &_path)
  513. {
  514. rmdir(_path.c_str());
  515. }
  516. #endif
  517. #ifdef _WIN32
  518. void File::_removeWindows(const string &_path)
  519. {
  520. _rmdir(_path.c_str());
  521. }
  522. #endif
  523. bool File::_renameTo(const string &_oldName, const string &_newName)
  524. {
  525. return rename(_oldName.c_str(), _newName.c_str()) == 0;
  526. }
  527. string File::_replaceWrongSeparator(string _path)
  528. {
  529. static const char unixSeparator = FilePathSeparator::getUnixFilePathSeparator();
  530. static const char windowsSeparator = FilePathSeparator::getWindowsFilePathSeparator();
  531. #if defined(unix) || defined(__APPLE__)
  532. replace(_path.begin(), _path.end(), windowsSeparator, unixSeparator);
  533. #endif
  534. #ifdef _WIN32
  535. replace(_path.begin(), _path.end(), unixSeparator, windowsSeparator);
  536. #endif
  537. return _path;
  538. }
  539. vector<string> File::_splitIntoSubDirectoryNames(const string &_path)
  540. {
  541. vector<string> subDirectoryNames{};
  542. stringstream _stream{_path};
  543. string subDirectoryName{};
  544. const char separator = FilePathSeparator::get();
  545. while (getline(_stream, subDirectoryName, separator))
  546. {
  547. subDirectoryNames.push_back(subDirectoryName);
  548. }
  549. return subDirectoryNames;
  550. }