ksIOUtils.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. {*******************************************************}
  2. { }
  3. { Methods of IO }
  4. { }
  5. { CopyRight (C) 2018-2020 KngStr }
  6. { }
  7. {*******************************************************}
  8. unit ksIOUtils;
  9. {$SCOPEDENUMS ON}
  10. interface
  11. uses
  12. {$IFDEF MSWINDOWS}
  13. Winapi.Windows,
  14. {$ENDIF}
  15. {$IFDEF POSIX}
  16. Posix.SysTypes, Posix.Errno, Posix.Unistd,
  17. {$ENDIF}
  18. {$IFDEF MACOS}
  19. {$IFDEF IOS}
  20. iOSApi.Foundation,
  21. {$ELSE !IOS}
  22. Macapi.CocoaTypes,
  23. {$ENDIF IOS}
  24. {$ENDIF MACOS}
  25. System.RTLConsts, System.SysUtils, System.Classes, System.Types, System.Masks,
  26. System.IOUtils;
  27. type
  28. TDirectoryEx = record
  29. public
  30. type
  31. TActionResult = (Accept, Skip, SkipAll, Stop);
  32. TFilterPredicate = reference to function(const Path: string;
  33. const SearchRec: TSearchRec): TActionResult;
  34. TDirectoryWalkProc = reference to function (const Path: string;
  35. const FileInfo: TSearchRec): TActionResult;
  36. private
  37. class procedure InternalCheckDirPathParam(const Path: string;
  38. const ExistsCheck: Boolean); static;
  39. class procedure CheckGetFilesParameters(Path: string;
  40. const SearchPattern: string); static;
  41. class function DoGetFiles(const Path, SearchPattern: string;
  42. const SearchOption: TSearchOption;
  43. const Predicate: TFilterPredicate;
  44. PreCallback: TDirectoryWalkProc = nil): TStringDynArray; static;
  45. class procedure WalkThroughDirectory(const Path, Pattern: string;
  46. const PreCallback, PostCallback: TDirectoryWalkProc;
  47. const Recursive: Boolean); static;
  48. public
  49. class function GetFiles(const Path: string): TStringDynArray;
  50. overload; inline; static;
  51. class function GetFiles(const Path: string;
  52. const Predicate: TFilterPredicate): TStringDynArray;
  53. overload; inline; static;
  54. class function GetFiles(const Path, SearchPattern: string): TStringDynArray;
  55. overload; inline; static;
  56. class function GetFiles(const Path, SearchPattern: string;
  57. const Predicate: TFilterPredicate): TStringDynArray;
  58. overload; inline; static;
  59. class function GetFiles(const Path, SearchPattern: string;
  60. const SearchOption: TSearchOption): TStringDynArray; overload; static;
  61. class function GetFiles(const Path, SearchPattern: string;
  62. const SearchOption: TSearchOption;
  63. const Predicate: TFilterPredicate): TStringDynArray; overload; static;
  64. class function GetFiles(const Path: string;
  65. const SearchOption: TSearchOption;
  66. const Predicate: TFilterPredicate): TStringDynArray; overload; static;
  67. class function GetFiles(const Path: string;
  68. PreCallback: TDirectoryWalkProc): TStringDynArray; overload; static;
  69. class function GetFiles(const Path: string;
  70. const Predicate: TFilterPredicate;
  71. PreCallback: TDirectoryWalkProc): TStringDynArray; overload; static;
  72. class function GetFiles(const Path: string;
  73. const SearchOption: TSearchOption;
  74. const Predicate: TFilterPredicate;
  75. PreCallback: TDirectoryWalkProc): TStringDynArray; overload; static;
  76. class function GetFiles(const Path, SearchPattern: string;
  77. const SearchOption: TSearchOption;
  78. const Predicate: TFilterPredicate;
  79. PreCallback: TDirectoryWalkProc): TStringDynArray; overload; static;
  80. end;
  81. TPathEx = record
  82. public
  83. const
  84. FCCurrentDir: string = '.'; // DO NOT LOCALIZE
  85. FCParentDir: string = '..'; // DO NOT LOCALIZE
  86. FCExtendedPrefix: string = '\\?\'; // DO NOT LOCALIZE
  87. FCExtendedUNCPrefix: string = '\\?\UNC\'; // DO NOT LOCALIZE
  88. private
  89. class procedure CheckPathLength(const Path: string; const MaxLength: Integer); static;
  90. class function DoCombine(const Path1, Path2: string;
  91. const ValidateParams: Boolean): string; static;
  92. class function DoGetFullPath(const Path: string): string; static;
  93. class function DoMatchesPattern(const FileName, Pattern: string): Boolean; inline; static;
  94. {$IFDEF MSWINDOWS}
  95. class function HasPathValidColon(const Path: string): Boolean; static;
  96. {$ENDIF MSWINDOWS}
  97. {$IFDEF MSWINDOWS}
  98. class function GetPosAfterExtendedPrefix(const Path: string): Integer;
  99. overload; inline; static;
  100. class function GetPosAfterExtendedPrefix(const Path: string;
  101. out Prefix: TPathPrefixType): Integer; overload; static;
  102. {$ENDIF MSWINDOWS}
  103. class function GetExtendedPrefix(const Path: string): TPathPrefixType; static;
  104. end;
  105. TFileEx = record
  106. private
  107. const
  108. FCMinFileNameLen = 12;
  109. end;
  110. /// <summary>
  111. /// load bak file if exist to avoid error file
  112. /// </summary>
  113. procedure SafeLoadFile(AFile: string; ACallback: TProc; ABakExt: string = '.bak');
  114. /// <summary>
  115. /// use bak file if exist to avoid error file
  116. /// </summary>
  117. procedure SafeSaveFile(AFile: string; ACallback: TProc; ABakExt: string = '.bak');
  118. implementation
  119. uses
  120. System.StrUtils;
  121. procedure SafeLoadFile(AFile: string; ACallback: TProc; ABakExt: string);
  122. var
  123. LFile: string;
  124. begin
  125. LFile := AFile + ABakExt;
  126. if FileExists(LFile) then begin
  127. if FileExists(AFile) then
  128. DeleteFile(AFile);
  129. RenameFile(LFile, AFile);
  130. end;
  131. if FileExists(AFile) then
  132. ACallback;
  133. end;
  134. procedure SafeSaveFile(AFile: string; ACallback: TProc; ABakExt: string);
  135. var
  136. LFile: string;
  137. begin
  138. LFile := AFile + ABakExt;
  139. if FileExists(LFile) then
  140. DeleteFile(LFile);
  141. if (not FileExists(AFile)) or (not RenameFile(AFile, LFile)) then
  142. LFile := '';
  143. ACallback;
  144. if LFile <> '' then
  145. DeleteFile(LFile);
  146. end;
  147. { TDirectoryEx }
  148. class procedure TDirectoryEx.CheckGetFilesParameters(Path: string;
  149. const SearchPattern: string);
  150. begin
  151. Path := TPathEx.DoGetFullPath(Path);
  152. if Trim(SearchPattern) = '' then // DO NOT LOCALIZE
  153. raise EArgumentException.CreateRes(@SInvalidCharsInSearchPattern);
  154. if not TPath.HasValidFileNameChars(SearchPattern, True) then
  155. raise EArgumentException.CreateRes(@SInvalidCharsInSearchPattern);
  156. InternalCheckDirPathParam(Path, True);
  157. end;
  158. class function TDirectoryEx.DoGetFiles(const Path, SearchPattern: string;
  159. const SearchOption: TSearchOption;
  160. const Predicate: TFilterPredicate;
  161. PreCallback: TDirectoryWalkProc): TStringDynArray;
  162. var
  163. ResultArray: TStringDynArray;
  164. LPreCallback: TDirectoryWalkProc;
  165. begin
  166. ResultArray := nil;
  167. LPreCallback :=
  168. function (const Path: string; const FileInfo: TSearchRec): TActionResult
  169. var
  170. CanAdd: Boolean;
  171. FIsDir: Boolean;
  172. begin
  173. Result := TActionResult.Accept;
  174. CanAdd := False;
  175. FIsDir := FileInfo.Attr and System.SysUtils.faDirectory <> 0;
  176. if FIsDir and ((FileInfo.Name = TPathEx.FCCurrentDir) or
  177. (FileInfo.Name = TPathEx.FCParentDir)) then
  178. Exit(TDirectoryEx.TActionResult.Skip);
  179. if Assigned (PreCallback) then
  180. Result := PreCallback(Path, FileInfo)
  181. else if (not FIsDir) and Assigned(Predicate) then
  182. Result := Predicate(Path, FileInfo);
  183. if (Result = TActionResult.Accept) and not FIsDir then
  184. CanAdd := True;
  185. if CanAdd then begin
  186. SetLength(ResultArray, Length(ResultArray) + 1);
  187. ResultArray[Length(ResultArray) - 1] := TPathEx.DoCombine(Path, FileInfo.Name, False);
  188. end;
  189. end;
  190. WalkThroughDirectory(Path, SearchPattern, LPreCallback, nil,
  191. SearchOption = TSearchOption.soAllDirectories);
  192. {$IFDEF LINUX}
  193. TArray.Sort<string>(ResultArray);
  194. {$ENDIF}
  195. Result := ResultArray;
  196. end;
  197. class function TDirectoryEx.GetFiles(const Path: string): TStringDynArray;
  198. begin
  199. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly); // DO NOT LOCALIZE
  200. end;
  201. class function TDirectoryEx.GetFiles(const Path,
  202. SearchPattern: string): TStringDynArray;
  203. begin
  204. Result := GetFiles(Path, SearchPattern, TSearchOption.soTopDirectoryOnly);
  205. end;
  206. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  207. const SearchOption: TSearchOption): TStringDynArray;
  208. begin
  209. Result := GetFiles(Path, SearchPattern, SearchOption, nil);
  210. end;
  211. class function TDirectoryEx.GetFiles(const Path: string;
  212. const SearchOption: TSearchOption;
  213. const Predicate: TFilterPredicate): TStringDynArray;
  214. begin
  215. Result := GetFiles(Path, '*', SearchOption, Predicate); // DO NOT LOCALIZE
  216. end;
  217. class function TDirectoryEx.GetFiles(const Path: string;
  218. const Predicate: TFilterPredicate): TStringDynArray;
  219. begin
  220. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, Predicate); // DO NOT LOCALIZE
  221. end;
  222. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  223. const Predicate: TFilterPredicate): TStringDynArray;
  224. begin
  225. Result := GetFiles(Path, SearchPattern, TSearchOption.soTopDirectoryOnly, Predicate);
  226. end;
  227. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  228. const SearchOption: TSearchOption;
  229. const Predicate: TFilterPredicate): TStringDynArray;
  230. begin
  231. Result := GetFiles(Path, SearchPattern, SearchOption, Predicate, nil);
  232. end;
  233. class function TDirectoryEx.GetFiles(const Path: string;
  234. PreCallback: TDirectoryWalkProc): TStringDynArray;
  235. begin
  236. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, nil, PreCallback); // DO NOT LOCALIZE
  237. end;
  238. class function TDirectoryEx.GetFiles(const Path: string;
  239. const Predicate: TFilterPredicate;
  240. PreCallback: TDirectoryWalkProc): TStringDynArray;
  241. begin
  242. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, Predicate, PreCallback); // DO NOT LOCALIZE
  243. end;
  244. class function TDirectoryEx.GetFiles(const Path: string;
  245. const SearchOption: TSearchOption; const Predicate: TFilterPredicate;
  246. PreCallback: TDirectoryWalkProc): TStringDynArray;
  247. begin
  248. Result := GetFiles(Path, '*', SearchOption, Predicate, PreCallback); // DO NOT LOCALIZE
  249. end;
  250. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  251. const SearchOption: TSearchOption; const Predicate: TFilterPredicate;
  252. PreCallback: TDirectoryWalkProc): TStringDynArray;
  253. begin
  254. CheckGetFilesParameters(Path, SearchPattern);
  255. Result := DoGetFiles(Path, SearchPattern, SearchOption, Predicate, PreCallback);
  256. end;
  257. class procedure TDirectoryEx.InternalCheckDirPathParam(const Path: string;
  258. const ExistsCheck: Boolean);
  259. begin
  260. TPathEx.CheckPathLength(Path, MAX_PATH {$IFDEF MSWINDOWS}- TFileEx.FCMinFileNameLen{$ENDIF});
  261. {$IFDEF MSWINDOWS}
  262. { Windows-only: Check for valid colon char in the path }
  263. if not TPathEx.HasPathValidColon(Path) then
  264. raise ENotSupportedException.CreateRes(@SPathFormatNotSupported);
  265. {$ENDIF MSWINDOWS}
  266. if Trim(Path) = '' then // DO NOT LOCALIZE
  267. raise EArgumentException.CreateRes(@SInvalidCharsInPath);
  268. if not TPath.HasValidPathChars(Path, False) then
  269. raise EArgumentException.CreateRes(@SInvalidCharsInPath);
  270. if ExistsCheck and (not TDirectory.Exists(Path)) then
  271. raise EDirectoryNotFoundException.CreateRes(@SPathNotFound);
  272. end;
  273. class procedure TDirectoryEx.WalkThroughDirectory(const Path, Pattern: string;
  274. const PreCallback, PostCallback: TDirectoryWalkProc;
  275. const Recursive: Boolean);
  276. var
  277. SearchRec: TSearchRec;
  278. Match: Boolean;
  279. Stop: Boolean;
  280. begin
  281. if FindFirst(TPathEx.DoCombine(Path, '*', False), faAnyFile, SearchRec) = 0 then // DO NOT LOCALIZE
  282. try
  283. Stop := False;
  284. repeat
  285. Match := TPathEx.DoMatchesPattern(SearchRec.Name, Pattern);
  286. // call the preorder callback method
  287. if Match and Assigned(PreCallback) then begin
  288. case PreCallback(Path, SearchRec) of
  289. TActionResult.Skip: Continue;
  290. TActionResult.SkipAll: Break;
  291. TActionResult.Stop: Stop := True;
  292. end;
  293. end;
  294. if not Stop then
  295. begin
  296. // go recursive in subdirectories
  297. if Recursive and (SearchRec.Attr and System.SysUtils.faDirectory <> 0) and
  298. (SearchRec.Name <> TPathEx.FCCurrentDir) and
  299. (SearchRec.Name <> TPathEx.FCParentDir) then
  300. WalkThroughDirectory(TPathEx.DoCombine(Path, SearchRec.Name, False),
  301. Pattern, PreCallback, PostCallback, Recursive);
  302. // call the post-order callback method
  303. if Match and Assigned(PostCallback) then begin
  304. case PostCallback(Path, SearchRec) of
  305. TActionResult.Skip: Continue;
  306. TActionResult.SkipAll: Break;
  307. TActionResult.Stop: Stop := True;
  308. end;
  309. end;
  310. end;
  311. until Stop or (FindNext(SearchRec) <> 0);
  312. finally
  313. FindClose(SearchRec);
  314. end;
  315. end;
  316. { TPathEx }
  317. class procedure TPathEx.CheckPathLength(const Path: string;
  318. const MaxLength: Integer);
  319. begin
  320. {$IFDEF MSWINDOWS}
  321. if (Length(Path) >= MaxLength) and (not TPath.IsExtendedPrefixed(Path)) then // Check the length in Chars on Win32
  322. {$ENDIF MSWINDOWS}
  323. {$IFDEF POSIX}
  324. if (Length(UTF8Encode(Path)) >= MaxLength) then // Check the length in bytes on POSIX
  325. {$ENDIF POSIX}
  326. raise EPathTooLongException.CreateRes(@SPathTooLong);
  327. end;
  328. class function TPathEx.DoCombine(const Path1, Path2: string;
  329. const ValidateParams: Boolean): string;
  330. begin
  331. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  332. Result := TPath.Combine(Path1, Path2);
  333. end;
  334. class function TPathEx.DoGetFullPath(const Path: string): string;
  335. begin
  336. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  337. Result := TPath.GetFullPath(Path);
  338. end;
  339. class function TPathEx.DoMatchesPattern(const FileName,
  340. Pattern: string): Boolean;
  341. begin
  342. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  343. Result := TPath.MatchesPattern(FileName, Pattern, True);
  344. end;
  345. class function TPathEx.GetExtendedPrefix(const Path: string): TPathPrefixType;
  346. {$IFDEF MSWINDOWS}
  347. begin
  348. Result := TPathPrefixType.pptNoPrefix;
  349. if Path <> '' then
  350. begin
  351. if Path.StartsWith(FCExtendedUNCPrefix) then
  352. Result := TPathPrefixType.pptExtendedUNC
  353. else
  354. if Path.StartsWith(FCExtendedPrefix) then
  355. Result := TPathPrefixType.pptExtended;
  356. end;
  357. end;
  358. {$ENDIF MSWINDOWS}
  359. {$IFDEF POSIX}
  360. begin
  361. Result := TPathPrefixType.pptNoPrefix; // No support for extended prefixes on Unixes
  362. end;
  363. {$ENDIF POSIX}
  364. {$IFDEF MSWINDOWS}
  365. class function TPathEx.GetPosAfterExtendedPrefix(const Path: string;
  366. out Prefix: TPathPrefixType): Integer;
  367. begin
  368. Prefix := GetExtendedPrefix(Path);
  369. case Prefix of
  370. TPathPrefixType.pptNoPrefix:
  371. Result := 1;
  372. TPathPrefixType.pptExtended:
  373. Result := Length(FCExtendedPrefix) + 1;
  374. TPathPrefixType.pptExtendedUNC:
  375. Result := Length(FCExtendedUNCPrefix) + 1;
  376. else
  377. Result := 1;
  378. end;
  379. end;
  380. class function TPathEx.GetPosAfterExtendedPrefix(const Path: string): Integer;
  381. var
  382. Prefix: TPathPrefixType;
  383. begin
  384. Result := GetPosAfterExtendedPrefix(Path, Prefix);
  385. end;
  386. class function TPathEx.HasPathValidColon(const Path: string): Boolean;
  387. var
  388. StartIdx: Integer;
  389. begin
  390. Result := True;
  391. if Trim(Path) <> '' then // DO NOT LOCALIZE
  392. begin
  393. StartIdx := GetPosAfterExtendedPrefix(Path);
  394. if TPath.IsDriveRooted(Path) then
  395. Inc(StartIdx, 2);
  396. Result := PosEx(TPath.VolumeSeparatorChar, Path, StartIdx) = 0;
  397. end;
  398. end;
  399. {$ENDIF MSWINDOWS}
  400. end.