ksIOUtils.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. implementation
  111. uses
  112. System.StrUtils;
  113. { TDirectoryEx }
  114. class procedure TDirectoryEx.CheckGetFilesParameters(Path: string;
  115. const SearchPattern: string);
  116. begin
  117. Path := TPathEx.DoGetFullPath(Path);
  118. if Trim(SearchPattern) = '' then // DO NOT LOCALIZE
  119. raise EArgumentException.CreateRes(@SInvalidCharsInSearchPattern);
  120. if not TPath.HasValidFileNameChars(SearchPattern, True) then
  121. raise EArgumentException.CreateRes(@SInvalidCharsInSearchPattern);
  122. InternalCheckDirPathParam(Path, True);
  123. end;
  124. class function TDirectoryEx.DoGetFiles(const Path, SearchPattern: string;
  125. const SearchOption: TSearchOption;
  126. const Predicate: TFilterPredicate;
  127. PreCallback: TDirectoryWalkProc): TStringDynArray;
  128. var
  129. ResultArray: TStringDynArray;
  130. LPreCallback: TDirectoryWalkProc;
  131. begin
  132. ResultArray := nil;
  133. LPreCallback :=
  134. function (const Path: string; const FileInfo: TSearchRec): TActionResult
  135. var
  136. CanAdd: Boolean;
  137. FIsDir: Boolean;
  138. begin
  139. Result := TActionResult.Accept;
  140. CanAdd := False;
  141. FIsDir := FileInfo.Attr and System.SysUtils.faDirectory <> 0;
  142. if FIsDir and ((FileInfo.Name = TPathEx.FCCurrentDir) or
  143. (FileInfo.Name = TPathEx.FCParentDir)) then
  144. Exit(TDirectoryEx.TActionResult.Skip);
  145. if Assigned (PreCallback) then
  146. Result := PreCallback(Path, FileInfo)
  147. else if (not FIsDir) and Assigned(Predicate) then
  148. Result := Predicate(Path, FileInfo);
  149. if (Result = TActionResult.Accept) and not FIsDir then
  150. CanAdd := True;
  151. if CanAdd then begin
  152. SetLength(ResultArray, Length(ResultArray) + 1);
  153. ResultArray[Length(ResultArray) - 1] := TPathEx.DoCombine(Path, FileInfo.Name, False);
  154. end;
  155. end;
  156. WalkThroughDirectory(Path, SearchPattern, LPreCallback, nil,
  157. SearchOption = TSearchOption.soAllDirectories);
  158. {$IFDEF LINUX}
  159. TArray.Sort<string>(ResultArray);
  160. {$ENDIF}
  161. Result := ResultArray;
  162. end;
  163. class function TDirectoryEx.GetFiles(const Path: string): TStringDynArray;
  164. begin
  165. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly); // DO NOT LOCALIZE
  166. end;
  167. class function TDirectoryEx.GetFiles(const Path,
  168. SearchPattern: string): TStringDynArray;
  169. begin
  170. Result := GetFiles(Path, SearchPattern, TSearchOption.soTopDirectoryOnly);
  171. end;
  172. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  173. const SearchOption: TSearchOption): TStringDynArray;
  174. begin
  175. Result := GetFiles(Path, SearchPattern, SearchOption, nil);
  176. end;
  177. class function TDirectoryEx.GetFiles(const Path: string;
  178. const SearchOption: TSearchOption;
  179. const Predicate: TFilterPredicate): TStringDynArray;
  180. begin
  181. Result := GetFiles(Path, '*', SearchOption, Predicate); // DO NOT LOCALIZE
  182. end;
  183. class function TDirectoryEx.GetFiles(const Path: string;
  184. const Predicate: TFilterPredicate): TStringDynArray;
  185. begin
  186. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, Predicate); // DO NOT LOCALIZE
  187. end;
  188. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  189. const Predicate: TFilterPredicate): TStringDynArray;
  190. begin
  191. Result := GetFiles(Path, SearchPattern, TSearchOption.soTopDirectoryOnly, Predicate);
  192. end;
  193. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  194. const SearchOption: TSearchOption;
  195. const Predicate: TFilterPredicate): TStringDynArray;
  196. begin
  197. Result := GetFiles(Path, SearchPattern, SearchOption, Predicate, nil);
  198. end;
  199. class function TDirectoryEx.GetFiles(const Path: string;
  200. PreCallback: TDirectoryWalkProc): TStringDynArray;
  201. begin
  202. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, nil, PreCallback); // DO NOT LOCALIZE
  203. end;
  204. class function TDirectoryEx.GetFiles(const Path: string;
  205. const Predicate: TFilterPredicate;
  206. PreCallback: TDirectoryWalkProc): TStringDynArray;
  207. begin
  208. Result := GetFiles(Path, '*', TSearchOption.soTopDirectoryOnly, Predicate, PreCallback); // DO NOT LOCALIZE
  209. end;
  210. class function TDirectoryEx.GetFiles(const Path: string;
  211. const SearchOption: TSearchOption; const Predicate: TFilterPredicate;
  212. PreCallback: TDirectoryWalkProc): TStringDynArray;
  213. begin
  214. Result := GetFiles(Path, '*', SearchOption, Predicate, PreCallback); // DO NOT LOCALIZE
  215. end;
  216. class function TDirectoryEx.GetFiles(const Path, SearchPattern: string;
  217. const SearchOption: TSearchOption; const Predicate: TFilterPredicate;
  218. PreCallback: TDirectoryWalkProc): TStringDynArray;
  219. begin
  220. CheckGetFilesParameters(Path, SearchPattern);
  221. Result := DoGetFiles(Path, SearchPattern, SearchOption, Predicate, PreCallback);
  222. end;
  223. class procedure TDirectoryEx.InternalCheckDirPathParam(const Path: string;
  224. const ExistsCheck: Boolean);
  225. begin
  226. TPathEx.CheckPathLength(Path, MAX_PATH {$IFDEF MSWINDOWS}- TFileEx.FCMinFileNameLen{$ENDIF});
  227. {$IFDEF MSWINDOWS}
  228. { Windows-only: Check for valid colon char in the path }
  229. if not TPathEx.HasPathValidColon(Path) then
  230. raise ENotSupportedException.CreateRes(@SPathFormatNotSupported);
  231. {$ENDIF MSWINDOWS}
  232. if Trim(Path) = '' then // DO NOT LOCALIZE
  233. raise EArgumentException.CreateRes(@SInvalidCharsInPath);
  234. if not TPath.HasValidPathChars(Path, False) then
  235. raise EArgumentException.CreateRes(@SInvalidCharsInPath);
  236. if ExistsCheck and (not TDirectory.Exists(Path)) then
  237. raise EDirectoryNotFoundException.CreateRes(@SPathNotFound);
  238. end;
  239. class procedure TDirectoryEx.WalkThroughDirectory(const Path, Pattern: string;
  240. const PreCallback, PostCallback: TDirectoryWalkProc;
  241. const Recursive: Boolean);
  242. var
  243. SearchRec: TSearchRec;
  244. Match: Boolean;
  245. Stop: Boolean;
  246. begin
  247. if FindFirst(TPathEx.DoCombine(Path, '*', False), faAnyFile, SearchRec) = 0 then // DO NOT LOCALIZE
  248. try
  249. Stop := False;
  250. repeat
  251. Match := TPathEx.DoMatchesPattern(SearchRec.Name, Pattern);
  252. // call the preorder callback method
  253. if Match and Assigned(PreCallback) then begin
  254. case PreCallback(Path, SearchRec) of
  255. TActionResult.Skip: Continue;
  256. TActionResult.SkipAll: Break;
  257. TActionResult.Stop: Stop := True;
  258. end;
  259. end;
  260. if not Stop then
  261. begin
  262. // go recursive in subdirectories
  263. if Recursive and (SearchRec.Attr and System.SysUtils.faDirectory <> 0) and
  264. (SearchRec.Name <> TPathEx.FCCurrentDir) and
  265. (SearchRec.Name <> TPathEx.FCParentDir) then
  266. WalkThroughDirectory(TPathEx.DoCombine(Path, SearchRec.Name, False),
  267. Pattern, PreCallback, PostCallback, Recursive);
  268. // call the post-order callback method
  269. if Match and Assigned(PostCallback) then begin
  270. case PostCallback(Path, SearchRec) of
  271. TActionResult.Skip: Continue;
  272. TActionResult.SkipAll: Break;
  273. TActionResult.Stop: Stop := True;
  274. end;
  275. end;
  276. end;
  277. until Stop or (FindNext(SearchRec) <> 0);
  278. finally
  279. FindClose(SearchRec);
  280. end;
  281. end;
  282. { TPathEx }
  283. class procedure TPathEx.CheckPathLength(const Path: string;
  284. const MaxLength: Integer);
  285. begin
  286. {$IFDEF MSWINDOWS}
  287. if (Length(Path) >= MaxLength) and (not TPath.IsExtendedPrefixed(Path)) then // Check the length in Chars on Win32
  288. {$ENDIF MSWINDOWS}
  289. {$IFDEF POSIX}
  290. if (Length(UTF8Encode(Path)) >= MaxLength) then // Check the length in bytes on POSIX
  291. {$ENDIF POSIX}
  292. raise EPathTooLongException.CreateRes(@SPathTooLong);
  293. end;
  294. class function TPathEx.DoCombine(const Path1, Path2: string;
  295. const ValidateParams: Boolean): string;
  296. begin
  297. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  298. Result := TPath.Combine(Path1, Path2);
  299. end;
  300. class function TPathEx.DoGetFullPath(const Path: string): string;
  301. begin
  302. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  303. Result := TPath.GetFullPath(Path);
  304. end;
  305. class function TPathEx.DoMatchesPattern(const FileName,
  306. Pattern: string): Boolean;
  307. begin
  308. { TODO -oAdministrator -c : not complete 2021-02-14 13:50:46 }
  309. Result := TPath.MatchesPattern(FileName, Pattern, True);
  310. end;
  311. class function TPathEx.GetExtendedPrefix(const Path: string): TPathPrefixType;
  312. {$IFDEF MSWINDOWS}
  313. begin
  314. Result := TPathPrefixType.pptNoPrefix;
  315. if Path <> '' then
  316. begin
  317. if Path.StartsWith(FCExtendedUNCPrefix) then
  318. Result := TPathPrefixType.pptExtendedUNC
  319. else
  320. if Path.StartsWith(FCExtendedPrefix) then
  321. Result := TPathPrefixType.pptExtended;
  322. end;
  323. end;
  324. {$ENDIF MSWINDOWS}
  325. {$IFDEF POSIX}
  326. begin
  327. Result := TPathPrefixType.pptNoPrefix; // No support for extended prefixes on Unixes
  328. end;
  329. {$ENDIF POSIX}
  330. {$IFDEF MSWINDOWS}
  331. class function TPathEx.GetPosAfterExtendedPrefix(const Path: string;
  332. out Prefix: TPathPrefixType): Integer;
  333. begin
  334. Prefix := GetExtendedPrefix(Path);
  335. case Prefix of
  336. TPathPrefixType.pptNoPrefix:
  337. Result := 1;
  338. TPathPrefixType.pptExtended:
  339. Result := Length(FCExtendedPrefix) + 1;
  340. TPathPrefixType.pptExtendedUNC:
  341. Result := Length(FCExtendedUNCPrefix) + 1;
  342. else
  343. Result := 1;
  344. end;
  345. end;
  346. class function TPathEx.GetPosAfterExtendedPrefix(const Path: string): Integer;
  347. var
  348. Prefix: TPathPrefixType;
  349. begin
  350. Result := GetPosAfterExtendedPrefix(Path, Prefix);
  351. end;
  352. class function TPathEx.HasPathValidColon(const Path: string): Boolean;
  353. var
  354. StartIdx: Integer;
  355. begin
  356. Result := True;
  357. if Trim(Path) <> '' then // DO NOT LOCALIZE
  358. begin
  359. StartIdx := GetPosAfterExtendedPrefix(Path);
  360. if TPath.IsDriveRooted(Path) then
  361. Inc(StartIdx, 2);
  362. Result := PosEx(TPath.VolumeSeparatorChar, Path, StartIdx) = 0;
  363. end;
  364. end;
  365. {$ENDIF MSWINDOWS}
  366. end.