ufilesystem.pas 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UFileSystem;
  3. {$mode objfpc}{$H+}{$MODESWITCH ADVANCEDRECORDS}
  4. interface
  5. uses
  6. Classes, SysUtils, UResourceStrings, LazUTF8, Forms, BGRAMultiFileType,
  7. ShellCtrls, fgl, Masks, LCLVersion;
  8. type
  9. TDeleteConfirmationFunction = function(AForm: TForm; const AFiles: array of string; AContained: boolean): boolean of object;
  10. const
  11. //Windows file systems
  12. fsFAT = 'FAT'; // FAT16 or FAT32
  13. fsNTFS = 'NTFS'; // Windows NT
  14. fsExFAT = 'exFAT'; // Extended FAT for flash drives
  15. //Removable disks file system
  16. fsCDFS = 'ISO 9660'; // CD-ROM
  17. fsUDF = 'UDF'; // Unversal Disk Format
  18. //Common Linux file systems
  19. fsMinix = 'minix'; // Minix file system
  20. fsExt2 = 'ext2'; // Linux ext2
  21. fsExt3 = 'ext3'; // Linux ext3
  22. fsExt4 = 'ext4'; // Linux ext4
  23. //more Linux file systems
  24. fsSysV = 'sysv'; // Unix System V
  25. fsXFS = 'XFS'; //IRIX
  26. fsJFS = 'JFS'; //AIX journaled file system
  27. fsXiaFS = 'xiafs'; //extension of Minix file system
  28. fsReiserFS = 'Reiserfs'; //ReiserFS
  29. //Apple file systems
  30. fsAPFS = 'APFS';
  31. fsHFS = 'HFS';
  32. //Misc
  33. fsHPFS = 'HPFS'; // OS/2 'High Performance File System'
  34. fsNWFS = 'NWFS'; // Novel NetWare File System
  35. //Network protocols
  36. fsNFS = 'nfs'; // Network File System
  37. fsSMB = 'smb'; // Server Message Block
  38. rsNCPFS = 'ncpfs'; // Novel Client
  39. type
  40. TFileSystemInfo = record
  41. name: string;
  42. path: string;
  43. device: string;
  44. fileSystem: string;
  45. longFilenames,
  46. caseSensitive,
  47. readonly: boolean;
  48. end;
  49. TFileSystemArray = array of TFileSystemInfo;
  50. { TFileInfo }
  51. TFileInfo = record
  52. Filename: string;
  53. LastModification: TDateTime;
  54. Size: int64;
  55. IsDirectory: boolean;
  56. class operator =(const fi1,fi2: TFileInfo): boolean;
  57. end;
  58. TFileInfoList = specialize TFPGList<TFileInfo>;
  59. { TFileManager }
  60. TFileManager = class
  61. function RemovePathTrail(ADir: string): string;
  62. procedure RemoveLastPathElement(var ADir: string; out ALastElement: string);
  63. function GetFileSystems: TFileSystemArray;
  64. function CanGetFileSystems: boolean;
  65. function MoveToTrash(AForm: TForm; const AFilenamesUTF8: array of string; AConfirmationCallback: TDeleteConfirmationFunction): boolean;
  66. function CreateFileStream(AFilenameUTF8: string; AMode: Word): TStream; overload;
  67. procedure CancelStreamAndFree(AStream: TStream);
  68. destructor Destroy; override;
  69. procedure GetDirectoryElements(const ABaseDir: string;
  70. AMask: string; AObjectTypes: TObjectTypes;
  71. AResult: TFileInfoList; AFileSortType: TFileSortType = fstNone);
  72. function IsDirectory(APathUTF8: string): boolean;
  73. function IsDirectoryEmpty(APathUTF8: string): boolean;
  74. function IsValidFileName(AName: string): boolean;
  75. procedure CreateDirectory(APathUTF8: string);
  76. function DeleteDirectory(APathUTF8: string): boolean;
  77. function FileExists(AFilenameUTF8: string): boolean;
  78. procedure DeleteFile(AFilenameUTF8: string);
  79. function GetValidFilename(ASuggested: string): string;
  80. function GetDefaultFilename(ADirectory: string): string;
  81. end;
  82. var
  83. FileManager: TFileManager;
  84. implementation
  85. uses BGRAUTF8, BGRAWinResource, BGRALazResource, LazFileUtils, Dialogs
  86. {$IFDEF WINDOWS}, Windows{$ENDIF}
  87. {$IFDEF LINUX}, Process{$ENDIF}
  88. {$IFDEF DARWIN}, Process{$ENDIF};
  89. type
  90. TExtendedFilename = record
  91. Filename: string;
  92. SubFilename: string;
  93. end;
  94. procedure LinuxBundleToFileSystem(ABundle: string; out AFilesystem: string;
  95. out ALongFilenames: boolean; out ACaseSensitive: boolean);
  96. begin
  97. if (ABundle = 'ntfs') or (ABundle = 'fuseblk') then AFilesystem := fsNTFS else
  98. if (ABundle = 'msdos') or (ABundle = 'umsdos') or (ABundle='vfat') then AFilesystem := fsFAT else
  99. if ABundle = 'iso9660' then AFilesystem:= fsCDFS else
  100. if ABundle = 'hpfs' then AFilesystem := fsHPFS else
  101. if ABundle = 'udf' then AFilesystem := fsUDF else
  102. if ABundle = 'ncp' then AFilesystem := fsNWFS else
  103. if ABundle = 'apfs' then AFilesystem := fsAPFS else
  104. if ABundle = 'hfs' then AFilesystem := fsHFS else
  105. if ABundle = 'exfat' then AFilesystem := fsExFAT else
  106. AFilesystem := ABundle;
  107. ALongFilenames := (ABundle <> 'minix') and (ABundle <> 'msdos');
  108. ACaseSensitive := (ABundle <> 'msdos') and (ABundle <> 'umsdos') and (ABundle <> 'vfat')
  109. and (ABundle <> 'exfat');
  110. end;
  111. {$IFDEF LINUX}
  112. const LinuxFileSystems: array[0..21] of string =
  113. ('minix', 'ext2', 'ext3', 'ext4',
  114. 'sysv', 'XFS', 'JFS', 'xiafs', 'Reiserfs',
  115. {FAT} 'msdos', 'umsdos', 'vfat', 'exfat', {NTFS} 'ntfs', 'fuseblk',
  116. {CDFS} 'iso9660', {UDF} 'udf',
  117. {HPFS} 'hpfs', {NWFS} 'ncp',
  118. 'nfs', 'smb', 'ncpfs');
  119. function ReadBooleanFromFile(AFilename: string): boolean;
  120. var t: textfile;
  121. s: string;
  122. begin
  123. assignfile(t, AFilename);
  124. reset(t);
  125. readln(t,s);
  126. closefile(t);
  127. result := trim(s)='1';
  128. end;
  129. function UnespacePath(APath: string): string;
  130. var
  131. i, charCode: Integer;
  132. begin
  133. result := APath;
  134. for i := length(result)-3 downto 1 do
  135. if (result[i]='\') and (result[i+1] in['0','1']) and
  136. (result[i+2] in ['0'..'9']) and (result[i+3] in ['0'..'9']) then
  137. begin
  138. charCode := (ord(result[i+3])-ord('0'))+
  139. (ord(result[i+2])-ord('0'))*8+
  140. (ord(result[i+1])-ord('0'))*64;
  141. delete(result,i+1,3);
  142. result[i] := chr(charCode);
  143. end;
  144. end;
  145. function GetLinuxFileSystems(AMountsFile: string): TFileSystemArray;
  146. var mtab: TextFile;
  147. desc: string;
  148. parsedDesc: TStringList;
  149. lFileSystem, removableInfo, lPath: string;
  150. i: integer;
  151. found, isRemovable: boolean;
  152. begin
  153. result := nil;
  154. parsedDesc := TStringList.Create;
  155. try
  156. AssignFile(mtab,AMountsFile);
  157. Reset(mtab);
  158. try
  159. while not Eof(mtab) do
  160. begin
  161. ReadLn(mtab,desc);
  162. parsedDesc.Delimiter := ' ';
  163. parsedDesc.DelimitedText := desc;
  164. if parsedDesc.Count >= 4 then
  165. begin
  166. lFileSystem:= parsedDesc[2];
  167. lPath := parsedDesc[1];
  168. found := false;
  169. for i := low(LinuxFileSystems) to high(LinuxFileSystems) do
  170. if LinuxFileSystems[i] = lFileSystem then
  171. begin
  172. found := true;
  173. break;
  174. end;
  175. if found and not lPath.StartsWith('/boot/') then
  176. begin
  177. setlength(result, length(result)+1);
  178. with result[high(result)] do
  179. begin
  180. fileSystem := lFileSystem;
  181. path := UnespacePath(parsedDesc[1]);
  182. device := parsedDesc[0];
  183. readonly:= (copy(parsedDesc[3],1,3) <> 'rw,') and (parsedDesc[3]<>'rw');
  184. //detecting device type
  185. if copy(device,1,5)='/dev/' then delete(device,1,5);
  186. if (copy(device,1,5) = 'disk/') or (copy(device,1,4) = 'dsk/') or
  187. (copy(device,1,2) = 'hd') then
  188. begin
  189. if fileSystem = 'iso9660' then
  190. device := rsCdRom
  191. else
  192. device := rsFixedDrive;
  193. end
  194. else
  195. if copy(device,1,2) = 'fd' then
  196. device := rsRemovableDrive
  197. else if copy(device,1,2) = 'sd' then
  198. begin
  199. removableInfo := '/sys/block/'+copy(device,1,3)+'/removable';
  200. if FileExists(removableInfo) then
  201. isRemovable := ReadBooleanFromFile(removableInfo)
  202. else
  203. isRemovable := false;
  204. if isRemovable then
  205. device := rsRemovableDrive
  206. else
  207. device := rsFixedDrive;
  208. end
  209. else if copy(device,1,3) = 'scd' then
  210. device := rsCdRom;
  211. if (fileSystem = 'nfs') or (fileSystem = 'smb') or (fileSystem = 'ncpfs') then device := rsNetworkDrive;
  212. //retrieving volume name
  213. if path = '/' then
  214. name := rsFileSystem
  215. else
  216. name := ExtractFileName(path);
  217. //formatting file system
  218. LinuxBundleToFileSystem(fileSystem, fileSystem, longFilenames, caseSensitive);
  219. end;
  220. end;
  221. end;
  222. end;
  223. finally
  224. CloseFile(mtab);
  225. end;
  226. except
  227. end;
  228. parsedDesc.Free;
  229. end;
  230. {$ENDIF}
  231. {$IFDEF WINDOWS}
  232. function GetWindowsFileSystems: TFileSystemArray;
  233. var
  234. Drive: Char;
  235. DrivePath: widestring;
  236. lDevice: string;
  237. volumeName,fileSystemName: packed array[1..MAX_PATH+1] of WideChar;
  238. maxFilenameLength,fileSystemFlags: DWord;
  239. begin
  240. result := nil;
  241. for Drive := 'A' to 'Z' do
  242. begin
  243. DrivePath := WideString(Drive + ':\');
  244. lDevice := '';
  245. case GetDriveTypeW(PWideChar(DrivePath)) of
  246. DRIVE_REMOVABLE: lDevice := rsRemovableDrive;
  247. DRIVE_FIXED: lDevice := rsFixedDrive;
  248. DRIVE_REMOTE: lDevice := rsNetworkDrive;
  249. DRIVE_CDROM: lDevice := rsCdRom;
  250. DRIVE_RAMDISK: lDevice := rsRamDisk;
  251. end;
  252. if lDevice <> '' then
  253. begin
  254. volumeName := '';
  255. fileSystemName := '';
  256. if GetVolumeInformationW(PWideChar(DrivePath), @volumeName, high(volumeName),
  257. nil, {%H-}maxFilenameLength, {%H-}fileSystemFlags, @fileSystemName, high(fileSystemName)) then
  258. begin
  259. setlength(result, length(result)+1);
  260. with result[high(result)] do
  261. begin
  262. device := lDevice;
  263. path := UTF8Encode(DrivePath);
  264. name := UTF16ToUTF8(PWideChar(@volumeName));
  265. fileSystem := UTF16ToUTF8(PWideChar(@fileSystemName));
  266. longFilenames:= maxFilenameLength >= 128;
  267. caseSensitive:= ((fileSystemFlags and $00000001) <> 0);
  268. readonly:= (fileSystemFlags and $00080000) <> 0;
  269. //formatting file system
  270. if (fileSystem = 'FAT') or (fileSystem = 'FAT32') then fileSystem := fsFAT else
  271. if fileSystem = 'CDFS' then fileSystem:= fsCDFS else
  272. if fileSystem = 'HPFS' then fileSystem:= fsHPFS else
  273. if fileSystem = 'UDF' then fileSystem := fsUDF else
  274. if fileSystem = 'NWFS' then fileSystem := fsNWFS;
  275. end;
  276. end;
  277. end;
  278. end;
  279. end;
  280. {$ENDIF}
  281. {$IFDEF DARWIN}
  282. var
  283. darwinFilesystemsDate: TDateTime;
  284. darwinFilesystemsCached: TFileSystemArray;
  285. function GetDarwinFileSystems: TFileSystemArray;
  286. procedure FindDevices;
  287. var
  288. runResult, headers, curLine, fs, mountPath: string;
  289. lines: TStringList;
  290. blocksPos, mountedPos, i, endFS: integer;
  291. count: integer;
  292. begin
  293. if not RunCommand('df',['-P'],runResult) then exit;
  294. lines := TStringList.Create;
  295. lines.Text:= runResult;
  296. headers := lines[0];
  297. blocksPos := pos('-blocks', headers);
  298. mountedPos := pos('Mounted on', headers);
  299. if (blocksPos <> 0) and (mountedPos <> 0) then
  300. begin
  301. inc(blocksPos, 5);
  302. count := 0;
  303. setlength(result, lines.Count-1);
  304. for i := 1 to lines.Count-1 do
  305. begin
  306. curLine := lines[i];
  307. endFS := blocksPos;
  308. if endFS > length(curLine) then continue;
  309. while (endFS > 1) and (curLine[endFS] in['0'..'9']) do dec(endFS);
  310. while (endFS > 1) and (curLine[endFS] in[#0..#32]) do dec(endFS);
  311. fs := copy(curLine,1,endFS);
  312. if fs.StartsWith('/dev/') then
  313. begin
  314. mountPath := copy(curLine, mountedPos, length(curLine)-mountedPos+1);
  315. if (mountPath <> '/var/vm') and (mountPath <> '/private/var/vm') then
  316. begin
  317. result[count].path := mountPath;
  318. inc(count);
  319. end;
  320. end;
  321. end;
  322. setlength(result, count);
  323. end;
  324. lines.Free;
  325. end;
  326. procedure FetchDiskInfo(var fsi: TFileSystemInfo);
  327. var
  328. runResult, curLine, key, value: string;
  329. lines: TStringList;
  330. posColon: SizeInt;
  331. i: Integer;
  332. begin
  333. if not RunCommand('diskutil',['info',fsi.path],runResult) then exit;
  334. lines := TStringList.Create;
  335. lines.Text:= runResult;
  336. fsi.name:= '';
  337. fsi.device := '?';
  338. fsi.fileSystem := '?';
  339. fsi.longFilenames:= true;
  340. for i := 0 to lines.Count-1 do
  341. begin
  342. curLine := lines[i];
  343. posColon := pos(':',curLine);
  344. if posColon <> 0 then
  345. begin
  346. key := copy(curLine,1,posColon-1).TrimLeft;
  347. value := copy(curLine,posColon+1,length(curLine)-posColon).Trim;
  348. if key = 'Optical Drive Type' then fsi.device := rsCdRom else
  349. if (key = 'Removable Media') and (fsi.device = '?') then
  350. begin
  351. if value = 'Fixed' then fsi.device := rsFixedDrive
  352. else fsi.device := rsRemovableDrive;
  353. end else
  354. if key = 'Type (Bundle)' then
  355. begin
  356. LinuxBundleToFileSystem(value, fsi.fileSystem, fsi.longFilenames, fsi.caseSensitive);
  357. end else
  358. if key = 'Volume Name' then fsi.name:= value else
  359. if key = 'Read-Only Volume' then fsi.readonly:= value='Yes';
  360. end;
  361. end;
  362. lines.Free;
  363. end;
  364. var i: integer;
  365. begin
  366. if (darwinFilesystemsDate <> 0) and (Now < darwinFilesystemsDate + (10/(60*60*24))) then
  367. exit(darwinFilesystemsCached);
  368. result := nil;
  369. FindDevices;
  370. for i := 0 to high(result) do
  371. FetchDiskInfo(result[i]);
  372. darwinFilesystemsCached := result;
  373. darwinFilesystemsDate:= Now;
  374. end;
  375. {$ENDIF}
  376. function TFileManager.RemovePathTrail(ADir: string): string;
  377. begin
  378. if (length(ADir)>=1) and (ADir[length(ADir)]=PathDelim) then
  379. begin
  380. if (length(ADir)>=2) and (ADir[length(ADir)-1]=PathDelim) then
  381. result := copy(ADir,1,length(ADir)-2)
  382. else
  383. result := copy(ADir,1,length(ADir)-1);
  384. end
  385. else
  386. result := ADir;
  387. end;
  388. procedure TFileManager.RemoveLastPathElement(var ADir: string; out ALastElement: string);
  389. var
  390. idx, idxEnd: Integer;
  391. begin
  392. ADir := RemovePathTrail(ADir);
  393. idx := length(ADir);
  394. idxEnd := idx;
  395. while (idx >= 1) and (ADir[idx] <> PathDelim) do dec(idx);
  396. ALastElement:= copy(ADir,idx+1,idxEnd-idx);
  397. ADir := copy(ADir,1,idx);
  398. end;
  399. function TFileManager.GetFileSystems: TFileSystemArray;
  400. begin
  401. {$IFDEF LINUX}
  402. result := GetLinuxFileSystems('/proc/mounts');
  403. if result = nil then result := GetLinuxFileSystems('/etc/mtab');
  404. {$ELSE}
  405. {$IFDEF WINDOWS}
  406. result := GetWindowsFileSystems;
  407. {$ELSE}
  408. {$IFDEF DARWIN}
  409. result := GetDarwinFileSystems;
  410. {$ELSE}
  411. result := nil;
  412. {$ENDIF}
  413. {$ENDIF}
  414. {$ENDIF}
  415. end;
  416. function TFileManager.CanGetFileSystems: boolean;
  417. begin
  418. {$IFDEF DARWIN}
  419. result := true;
  420. {$ELSE}
  421. result := length(GetFileSystems)>0;
  422. {$ENDIF}
  423. end;
  424. {$IFDEF WINDOWS}
  425. type
  426. {$PUSH}{$PACKRECORDS C}
  427. SHFILEOPSTRUCTW = record
  428. hwnd : HWND;
  429. wFunc : UINT;
  430. pFrom : LPCWSTR;
  431. pTo : LPCWSTR;
  432. fFlags : FILEOP_FLAGS;
  433. fAnyOperationsAborted : WINBOOL;
  434. hNameMappings : LPVOID;
  435. lpszProgressTitle : LPCWSTR;
  436. end;
  437. {$POP}
  438. function SHFileOperationW(Var para1: SHFILEOPSTRUCTW):longint; stdcall; external 'shell32' name 'SHFileOperationW';
  439. function MoveToTrashOnWindows(AForm: TForm; const AFilenamesUTF8: array of string; {%H-}AConfirmationCallback: TDeleteConfirmationFunction): boolean;
  440. const FOF_ALLOWUNDO = $40;
  441. FO_DELETE = 3;
  442. var
  443. struct: SHFILEOPSTRUCTW;
  444. errorCode: longint;
  445. filenamesW: unicodestring;
  446. i: Integer;
  447. begin
  448. filenamesW := '';
  449. for i := 0 to high(AFilenamesUTF8) do
  450. filenamesW += UTF8ToUTF16(AFilenamesUTF8[i]) + #0; //this is a list of filenames, it is double terminated
  451. struct.hwnd := AForm.Handle;
  452. struct.wFunc := FO_DELETE;
  453. struct.pFrom := PWideChar(filenamesW);
  454. struct.pTo := nil;
  455. struct.fFlags := FOF_ALLOWUNDO;
  456. struct.lpszProgressTitle := nil;
  457. struct.fAnyOperationsAborted:= false;
  458. struct.hNameMappings := nil;
  459. errorCode := SHFileOperationW(struct);
  460. if errorCode = 0 then
  461. result := not struct.fAnyOperationsAborted
  462. else
  463. result := false;
  464. end;
  465. {$ENDIF}
  466. {$IFDEF LINUX}
  467. function MoveToTrashOnLinux(AForm: TForm; const AFilenamesUTF8: array of string; AConfirmationCallback: TDeleteConfirmationFunction): boolean;
  468. const gvfsTrash = '/usr/bin/gvfs-trash';
  469. trashPut = '/usr/bin/trash-put';
  470. function DoTrash(prog: string): boolean;
  471. var p: TProcess;
  472. i: integer;
  473. begin
  474. result := false;
  475. if Assigned(AConfirmationCallback) then
  476. begin
  477. if not AConfirmationCallback(AForm, AFilenamesUTF8, False) then exit;
  478. end;
  479. try
  480. p := TProcess.Create(nil);
  481. p.Executable := prog;
  482. for i := 0 to high(AFilenamesUTF8) do
  483. p.Parameters.Add(AFilenamesUTF8[i]);
  484. p.Options := [poWaitOnExit];
  485. p.Execute;
  486. p.Free;
  487. result := true;
  488. for i := 0 to high(AFilenamesUTF8) do
  489. if FileExists(AFilenamesUTF8[i]) then result := false;
  490. except
  491. end;
  492. end;
  493. begin
  494. if FileExists(gvfsTrash) then result := DoTrash(gvfsTrash)
  495. else if FileExists(trashPut) then result := DoTrash(trashPut)
  496. else
  497. result := false;
  498. end;
  499. {$ENDIF}
  500. {$IFDEF DARWIN}
  501. function RunAppleScriptLine(AScriptLine: string): boolean;
  502. var
  503. p: TProcess;
  504. begin
  505. p := nil;
  506. try
  507. p := TProcess.Create(nil);
  508. p.Executable := 'osascript';
  509. p.Parameters.Add('-e');
  510. p.Parameters.Add(AScriptLine);
  511. p.Options := [poWaitOnExit];
  512. p.Execute;
  513. result := true;
  514. except
  515. result := false;
  516. end;
  517. p.Free;
  518. end;
  519. function AppleScriptEscape(AText: string): string;
  520. begin
  521. result := StringReplace(AText, '\', '\\', [rfReplaceAll]);
  522. result := StringReplace(result, '"', '\"', [rfReplaceAll]);
  523. end;
  524. function MoveToTrashOnMacOS(AForm: TForm; const AFilenamesUTF8: array of string; AConfirmationCallback: TDeleteConfirmationFunction): boolean;
  525. var
  526. appleScript: String;
  527. i: Integer;
  528. begin
  529. if length(AFilenamesUTF8) = 0 then exit(true);
  530. if Assigned(AConfirmationCallback) then
  531. begin
  532. if not AConfirmationCallback(AForm, AFilenamesUTF8, False) then exit(false);
  533. end;
  534. appleScript := 'tell application "Finder" to delete {';
  535. for i := 0 to high(AFilenamesUTF8) do
  536. begin
  537. if i > 0 then appleScript += ', ';
  538. appleScript += 'POSIX file "' + AppleScriptEscape(AFilenamesUTF8[i]) + '"';
  539. end;
  540. appleScript += '}';
  541. if not RunAppleScriptLine(appleScript) then exit(false);
  542. result := true;
  543. for i := 0 to high(AFilenamesUTF8) do
  544. if FileExists(AFilenamesUTF8[i]) then result := false;
  545. end;
  546. {$ENDIF}
  547. function IsMultiFileContainerName(AFilenameUTF8: string): boolean;
  548. var
  549. ext: String;
  550. begin
  551. ext := UTF8LowerCase(ExtractFileExt(AFilenameUTF8));
  552. result := ((ext = '.lrs') or (ext = '.res'));
  553. end;
  554. function IsMultiFileContainer(AFilenameUTF8: string): boolean;
  555. begin
  556. result := IsMultiFileContainerName(AFilenameUTF8) and FileExistsUTF8(AFilenameUTF8);
  557. end;
  558. function ParseExtendedFilename(AFilenameUTF8: string): TExtendedFilename;
  559. var p: string;
  560. begin
  561. p := ExcludeTrailingPathDelimiter(ExtractFilePath(AFilenameUTF8));
  562. if IsMultiFileContainer(p) then
  563. begin
  564. result.Filename:= p;
  565. result.SubFilename := ExtractFileName(AFilenameUTF8);
  566. end else
  567. begin //regular file
  568. result.Filename:= AFilenameUTF8;
  569. result.SubFilename := '';
  570. end;
  571. end;
  572. function TFileManager.MoveToTrash(AForm: TForm; const AFilenamesUTF8: array of string; AConfirmationCallback: TDeleteConfirmationFunction): boolean;
  573. var
  574. i: integer;
  575. realFiles, containedFiles: array of string;
  576. nbRealFiles, nbContainedFiles: integer;
  577. begin
  578. if length(AFilenamesUTF8) = 0 then
  579. begin
  580. result := true;
  581. exit;
  582. end;
  583. nbRealFiles:= 0;
  584. realFiles := nil;
  585. setlength(realFiles, length(AFilenamesUTF8));
  586. nbContainedFiles:= 0;
  587. containedFiles := nil;
  588. setlength(containedFiles, length(AFilenamesUTF8));
  589. for i := 0 to high(AFilenamesUTF8) do
  590. if ParseExtendedFilename(AFilenamesUTF8[i]).SubFilename = '' then
  591. begin
  592. realFiles[nbRealFiles] := AFilenamesUTF8[i];
  593. inc(nbRealFiles);
  594. end else
  595. begin
  596. containedFiles[nbContainedFiles] := AFilenamesUTF8[i];
  597. inc(nbContainedFiles);
  598. end;
  599. setlength(realFiles, nbRealFiles);
  600. setlength(containedFiles, nbContainedFiles);
  601. if nbContainedFiles > 0 then
  602. begin
  603. if not AConfirmationCallback(AForm, containedFiles, True) then exit(false);
  604. try
  605. for i := 0 to high(containedFiles) do
  606. DeleteFile(containedFiles[i]);
  607. except on ex: exception do
  608. begin
  609. ShowMessage(ex.Message);
  610. exit(false);
  611. end;
  612. end;
  613. end;
  614. if nbRealFiles > 0 then
  615. begin
  616. {$IFDEF LINUX}
  617. result := MoveToTrashOnLinux(AForm, realFiles, AConfirmationCallback);
  618. {$ELSE}
  619. {$IFDEF WINDOWS}
  620. result := MoveToTrashOnWindows(AForm, realFiles, AConfirmationCallback);
  621. {$ELSE}
  622. {$IFDEF DARWIN}
  623. result := MoveToTrashOnMacOS(AForm, realFiles, AConfirmationCallback);
  624. {$ELSE}
  625. result := false;
  626. {$ENDIF}
  627. {$ENDIF}
  628. {$ENDIF}
  629. end;
  630. end;
  631. type
  632. { TStreamOverwriter }
  633. TStreamOverwriter = class(TFileStreamUTF8)
  634. protected
  635. FTempFilename: string;
  636. FFinalFilename: string;
  637. public
  638. constructor Create(AFilename: string);
  639. procedure Cancel;
  640. destructor Destroy; override;
  641. end;
  642. TOnDestroyStreamInsideMultifile = procedure(ASender: TObject);
  643. { TStreamInsideMultifile }
  644. TStreamInsideMultifile = class(TMemoryStream)
  645. private
  646. FOnDestroy: TOnDestroyStreamInsideMultifile;
  647. procedure SetContainer(AValue: TMultiFileContainer);
  648. procedure SetOnDestroy(AValue: TOnDestroyStreamInsideMultifile);
  649. protected
  650. FContainer: TMultiFileContainer;
  651. FName: string;
  652. FExtension: string;
  653. public
  654. constructor Create(AContainer: TMultiFileContainer; AName: string; AExtension: string);
  655. destructor Destroy; override;
  656. property Container: TMultiFileContainer read FContainer write SetContainer;
  657. property OnDestroy: TOnDestroyStreamInsideMultifile read FOnDestroy write SetOnDestroy;
  658. property Name: string read FName;
  659. property Extension: string read FExtension;
  660. end;
  661. var
  662. CurrentMultiFile: TMultiFileContainer;
  663. CurrentMultiFileName: string;
  664. CurrentMultiFileAge: Longint;
  665. CurrentStreams : array of TStreamInsideMultifile;
  666. function LoadMultiFile(AFilenameUTF8: string): TMultiFileContainer;
  667. begin
  668. case UTF8LowerCase(ExtractFileExt(AFilenameUTF8)) of
  669. '.res': result := TWinResourceContainer.Create(AFilenameUTF8);
  670. '.lrs': result := TLazResourceContainer.Create(AFilenameUTF8);
  671. else
  672. raise exception.Create('Unknown container type');
  673. end;
  674. end;
  675. procedure SetCurrentMultiFile(AFilenameUTF8: string);
  676. var newMulti: TMultiFileContainer;
  677. i: Integer;
  678. begin
  679. if {$IFDEF WINDOWS}UTF8CompareText(AFilenameUTF8, CurrentMultiFileName)=0{$ELSE}
  680. AFilenameUTF8 = CurrentMultiFileName{$ENDIF} then
  681. begin
  682. try
  683. if FileAgeUTF8(AFilenameUTF8) = CurrentMultiFileAge then exit;
  684. except
  685. end;
  686. end;
  687. if length(CurrentStreams)> 0 then
  688. begin
  689. MessageDlg(rsFileSystem, 'Some streams were still open when switching multifile container', mtWarning, [mbOk], 0);
  690. for i := 0 to high(CurrentStreams) do
  691. CurrentStreams[i].Container := nil;
  692. CurrentStreams := nil;
  693. end;
  694. newMulti := LoadMultiFile(AFilenameUTF8);
  695. FreeAndNil(CurrentMultiFile);
  696. CurrentMultiFile := newMulti;
  697. CurrentMultiFileName := AFilenameUTF8;
  698. CurrentMultiFileAge := FileAgeUTF8(AFilenameUTF8);
  699. end;
  700. procedure HandleStreamDestruction(ASender: TObject);
  701. var i, j: integer;
  702. begin
  703. for i := 0 to High(CurrentStreams) do
  704. if CurrentStreams[i] = ASender then
  705. begin
  706. for j := i to High(CurrentStreams)-1 do
  707. CurrentStreams[j] := CurrentStreams[j+1];
  708. setlength(CurrentStreams, length(CurrentStreams)-1);
  709. if Assigned(CurrentMultiFile) then
  710. begin
  711. try
  712. SetCurrentMultiFile(CurrentMultiFileName);
  713. CurrentMultiFile.Add(TStreamInsideMultifile(ASender).Name,TStreamInsideMultifile(ASender).Extension,TStreamInsideMultifile(ASender),true,false);
  714. CurrentMultiFile.SaveToFile(CurrentMultiFileName);
  715. CurrentMultiFileAge := FileAgeUTF8(CurrentMultiFileName);
  716. except
  717. MessageDlg(rsFileSystem, rsFileNotSaved, mtWarning, [mbOk], 0);
  718. end;
  719. end;
  720. exit;
  721. end;
  722. end;
  723. function InternalCreateFileStream(AExtendedFilename: TExtendedFilename; AMode: Word): TStream;
  724. var
  725. name,ext: string;
  726. index: Integer;
  727. begin
  728. if AExtendedFilename.SubFilename = '' then
  729. begin
  730. if ((AMode and not $00F0) = fmCreate) and (FileExistsUTF8(AExtendedFilename.Filename)) then
  731. result := TStreamOverwriter.Create(AExtendedFilename.Filename)
  732. else
  733. result := TFileStreamUTF8.Create(AExtendedFilename.Filename, AMode);
  734. end
  735. else
  736. begin
  737. SetCurrentMultiFile(AExtendedFilename.Filename);
  738. name := ChangeFileExt(AExtendedFilename.SubFilename,'');
  739. ext := ExtractFileExt(AExtendedFilename.SubFilename);
  740. if (length(ext)>0) and (ext[1]='.') then Delete(ext,1,1);
  741. index := CurrentMultiFile.IndexOf(name,ext);
  742. if (AMode and not $00F0) = fmOpenRead then
  743. begin
  744. if (index = -1) then
  745. raise exception.Create('File not found in container');
  746. result := TMemoryStream.Create;
  747. CurrentMultiFile.Entry[index].CopyTo(result);
  748. result.Position:= 0;
  749. end else
  750. if (AMode and not $00F0) = fmCreate then
  751. begin
  752. result := TStreamInsideMultifile.Create(CurrentMultiFile, name,ext);
  753. TStreamInsideMultifile(result).OnDestroy := @HandleStreamDestruction;
  754. setlength(CurrentStreams, length(CurrentStreams)+1);
  755. CurrentStreams[high(CurrentStreams)] := TStreamInsideMultifile(result);
  756. end else
  757. if (AMode and not $00F0) = fmOpenReadWrite then
  758. begin
  759. result := TStreamInsideMultifile.Create(CurrentMultiFile, name,ext);
  760. TStreamInsideMultifile(result).OnDestroy := @HandleStreamDestruction;
  761. CurrentMultiFile.Entry[index].CopyTo(result);
  762. result.Position:= 0;
  763. setlength(CurrentStreams, length(CurrentStreams)+1);
  764. CurrentStreams[high(CurrentStreams)] := TStreamInsideMultifile(result);
  765. end else
  766. raise exception.Create('Access mode not supported');
  767. end;
  768. end;
  769. { TStreamOverwriter }
  770. constructor TStreamOverwriter.Create(AFilename: string);
  771. begin
  772. FTempFilename:= SysUtils.GetTempFileName(ExtractFilePath(AFilename), '');
  773. FFinalFilename := AFilename;
  774. inherited Create(FTempFilename, fmCreate);
  775. end;
  776. procedure TStreamOverwriter.Cancel;
  777. begin
  778. FFinalFilename:= '';
  779. end;
  780. destructor TStreamOverwriter.Destroy;
  781. begin
  782. inherited Destroy;
  783. if FFinalFilename <> '' then
  784. begin
  785. if FileExistsUTF8(FFinalFilename) then DeleteFileUTF8(FFinalFilename);
  786. RenameFileUTF8(FTempFilename, FFinalFilename);
  787. end else
  788. DeleteFileUTF8(FTempFilename);
  789. end;
  790. function TFileManager.CreateFileStream(AFilenameUTF8: string; AMode: Word): TStream;
  791. begin
  792. result := InternalCreateFileStream(ParseExtendedFilename(AFilenameUTF8), AMode);
  793. end;
  794. procedure TFileManager.CancelStreamAndFree(AStream: TStream);
  795. var
  796. i, j: Integer;
  797. begin
  798. if AStream is TStreamInsideMultifile then
  799. with TStreamInsideMultifile(AStream) do
  800. begin
  801. OnDestroy:= nil;
  802. for i := 0 to High(CurrentStreams) do
  803. if CurrentStreams[i] = AStream then
  804. begin
  805. for j := i to High(CurrentStreams)-1 do
  806. CurrentStreams[j] := CurrentStreams[j+1];
  807. setlength(CurrentStreams, length(CurrentStreams)-1);
  808. break;
  809. end;
  810. end else
  811. if AStream is TStreamOverwriter then
  812. with TStreamOverwriter(AStream) do
  813. begin
  814. Cancel;
  815. end;
  816. AStream.Free;
  817. end;
  818. destructor TFileManager.Destroy;
  819. begin
  820. FreeAndNil(CurrentMultiFile);
  821. CurrentMultiFileName := '';
  822. inherited Destroy;
  823. end;
  824. function CompareFileInfoAlphabetically(const fi1, fi2: TFileInfo): integer;
  825. begin
  826. result := UTF8CompareText(fi1.Filename, fi2.Filename);
  827. end;
  828. function CompareFileInfoFoldersFirst(const fi1, fi2: TFileInfo): integer;
  829. begin
  830. if fi1.IsDirectory then
  831. begin
  832. if fi2.IsDirectory then
  833. result := UTF8CompareText(fi1.Filename, fi2.Filename)
  834. else
  835. result := 1;
  836. end else
  837. begin
  838. if not fi2.IsDirectory then
  839. result := UTF8CompareText(fi1.Filename, fi2.Filename)
  840. else
  841. result := -1;
  842. end;
  843. end;
  844. { TStreamInsideMultifile }
  845. procedure TStreamInsideMultifile.SetContainer(AValue: TMultiFileContainer);
  846. begin
  847. if FContainer=AValue then Exit;
  848. FContainer:=AValue;
  849. end;
  850. procedure TStreamInsideMultifile.SetOnDestroy(
  851. AValue: TOnDestroyStreamInsideMultifile);
  852. begin
  853. if FOnDestroy=AValue then Exit;
  854. FOnDestroy:=AValue;
  855. end;
  856. constructor TStreamInsideMultifile.Create(AContainer: TMultiFileContainer;
  857. AName: string; AExtension: string);
  858. begin
  859. FContainer := AContainer;
  860. FName := AName;
  861. FExtension:= AExtension;
  862. end;
  863. destructor TStreamInsideMultifile.Destroy;
  864. begin
  865. if Assigned(FOnDestroy) then
  866. FOnDestroy(self);
  867. inherited Destroy;
  868. end;
  869. { TFileInfo }
  870. class operator TFileInfo.=(const fi1, fi2: TFileInfo): boolean;
  871. begin
  872. result := fi1.Filename = fi2.Filename;
  873. end;
  874. function MaskAccepts(const AMask, AName, AExt: string): boolean;
  875. var
  876. maskStart,maskEnd,maskDot: integer;
  877. currentNameMask,currentExtMask: string;
  878. function NextMask: boolean;
  879. begin
  880. maskStart := maskEnd;
  881. while (maskStart < length(AMask)) and (AMask[maskStart] in[';',' ']) do inc(maskStart);
  882. maskEnd := maskStart;
  883. while (maskEnd < length(AMask)) and not (AMask[maskEnd] in[';',' ']) do inc(maskEnd);
  884. if maskEnd > maskStart then
  885. begin
  886. maskDot := maskStart;
  887. while (maskDot < maskEnd) and (AMask[maskDot] <> '.') do inc(maskDot);
  888. currentNameMask := copy(AMask,maskStart,maskDot-maskStart);
  889. if maskDot < maskEnd then
  890. currentExtMask := copy(AMask,maskDot+1,maskEnd-(maskDot+1))
  891. else
  892. currentExtMask := '';
  893. result := true;
  894. end else
  895. result := false;
  896. end;
  897. begin
  898. maskStart := 1;
  899. maskEnd := 1;
  900. maskDot := 1;
  901. currentNameMask:= '';
  902. currentExtMask := '';
  903. if not NextMask then
  904. result := true
  905. else
  906. begin
  907. repeat
  908. if ((currentNameMask = '*') or (currentNameMask = AName)) and
  909. ((currentExtMask = '*') or (currentExtMask = AExt)) then
  910. begin
  911. result := true;
  912. exit;
  913. end;
  914. until not NextMask;
  915. result := false;
  916. end;
  917. end;
  918. {$IF (LCL_FULLVERSION>=2020000)}
  919. {$DEFINE CUSTOM_GETFILESINDIR}
  920. {$ENDIF}
  921. {$IFDEF CUSTOM_GETFILESINDIR}
  922. {$i getfilesindir.inc}
  923. {$ENDIF}
  924. procedure TFileManager.GetDirectoryElements(const ABaseDir: string; AMask: string;
  925. AObjectTypes: TObjectTypes; AResult: TFileInfoList; AFileSortType: TFileSortType);
  926. var p: string;
  927. temp: TStringList;
  928. fi: TFileInfo;
  929. fullname: string;
  930. age: LongInt;
  931. i: Integer;
  932. entry: TMultiFileEntry;
  933. begin
  934. if AMask = '' then AMask := '*';
  935. p := ExcludeTrailingPathDelimiter(ABaseDir);
  936. if IsMultiFileContainer(p) then
  937. begin
  938. try
  939. if otNonFolders in AObjectTypes then
  940. begin
  941. SetCurrentMultiFile(p);
  942. age := FileAgeUTF8(p);
  943. try
  944. fi.LastModification := FileDateToDateTime(age);
  945. except
  946. fi.LastModification:= Now;
  947. end;
  948. for i := 0 to CurrentMultiFile.Count-1 do
  949. begin
  950. entry := CurrentMultiFile.Entry[i];
  951. if entry is TCustomResourceEntry then
  952. begin
  953. if TCustomResourceEntry(entry).LanguageId <> 0 then continue;
  954. end;
  955. if MaskAccepts(AMask, entry.Name, entry.Extension) then
  956. begin
  957. fi.IsDirectory := false;
  958. fi.Filename := entry.Name+'.'+entry.Extension;
  959. fi.Size := entry.FileSize;
  960. AResult.Add(fi)
  961. end;
  962. end;
  963. end;
  964. except
  965. end;
  966. end else
  967. begin
  968. temp := TStringList.Create;
  969. temp.OwnsObjects := true;
  970. {$IFNDEF CUSTOM_GETFILESINDIR}TCustomShellTreeView.{$ENDIF}GetFilesInDir(ABaseDir,AMask,AObjectTypes,temp,fstNone);
  971. for i := 0 to temp.Count-1 do
  972. begin
  973. fullname := IncludeTrailingPathDelimiter(ABaseDir)+temp[i];
  974. if IsMultiFileContainer(fullname) then continue;
  975. if AObjectTypes = [otFolders] then
  976. fi.IsDirectory := true
  977. else if not (otFolders in AObjectTypes) then
  978. fi.IsDirectory := false
  979. else
  980. fi.IsDirectory := DirectoryExistsUTF8(fullname);
  981. fi.Filename:= temp[i];
  982. age := FileAgeUTF8(fullname);
  983. if age = -1 then
  984. fi.LastModification:= 0
  985. else
  986. begin
  987. try
  988. fi.LastModification := FileDateToDateTime(age);
  989. except
  990. fi.LastModification:= 0;
  991. end;
  992. end;
  993. fi.Size := FileSizeUtf8(fullname);
  994. AResult.Add(fi);
  995. end;
  996. if otFolders in AObjectTypes then
  997. begin
  998. temp.Clear;
  999. {$IFNDEF CUSTOM_GETFILESINDIR}TCustomShellTreeView.{$ENDIF}GetFilesInDir(ABaseDir,'*.res;*.Res;*.RES;*.lrs;*.Lrs;*.LRS',[otNonFolders],temp,fstNone);
  1000. for i := 0 to temp.Count-1 do
  1001. begin
  1002. fullname := IncludeTrailingPathDelimiter(ABaseDir)+temp[i];
  1003. fi.IsDirectory := true;
  1004. fi.Filename:= temp[i];
  1005. age := FileAgeUTF8(fullname);
  1006. if age = -1 then
  1007. fi.LastModification:= 0
  1008. else
  1009. begin
  1010. try
  1011. fi.LastModification := FileDateToDateTime(age);
  1012. except
  1013. fi.LastModification:= 0;
  1014. end;
  1015. end;
  1016. fi.Size := FileSizeUtf8(fullname);
  1017. AResult.Add(fi);
  1018. end;
  1019. end;
  1020. temp.Free;
  1021. end;
  1022. case AFileSortType of
  1023. fstAlphabet: AResult.Sort(@CompareFileInfoAlphabetically);
  1024. fstFoldersFirst: AResult.Sort(@CompareFileInfoFoldersFirst);
  1025. end;
  1026. end;
  1027. function TFileManager.IsDirectory(APathUTF8: string): boolean;
  1028. begin
  1029. result := IsMultiFileContainer(RemovePathTrail(APathUTF8)) or DirectoryExistsUTF8(APathUTF8);
  1030. end;
  1031. function TFileManager.IsDirectoryEmpty(APathUTF8: string): boolean;
  1032. var searchRec: TSearchRec;
  1033. begin
  1034. if FindFirstUTF8(AppendPathDelim(APathUTF8) + '*.*', faAnyFile, searchRec) = 0 then
  1035. repeat
  1036. if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
  1037. begin
  1038. result := false;
  1039. FindCloseUTF8(searchRec);
  1040. exit;
  1041. end;
  1042. until FindNextUTF8(searchRec)<>0;
  1043. FindCloseUTF8(searchRec);
  1044. result := true;
  1045. end;
  1046. function TFileManager.IsValidFileName(AName: string): boolean;
  1047. begin
  1048. result := AName = GetValidFilename(AName);
  1049. end;
  1050. procedure TFileManager.CreateDirectory(APathUTF8: string);
  1051. var
  1052. str: TStream;
  1053. begin
  1054. if not IsMultiFileContainerName(APathUTF8) then
  1055. CreateDirUTF8(APathUTF8)
  1056. else
  1057. begin
  1058. str := CreateFileStream(APathUTF8, fmCreate);
  1059. str.Free;
  1060. end;
  1061. end;
  1062. function TFileManager.DeleteDirectory(APathUTF8: string): boolean;
  1063. begin
  1064. result := RemoveDirUTF8(APathUTF8);
  1065. end;
  1066. function TFileManager.FileExists(AFilenameUTF8: string): boolean;
  1067. var exFilename: TExtendedFilename;
  1068. ext: string;
  1069. begin
  1070. exFilename := ParseExtendedFilename(AFilenameUTF8);
  1071. if exFilename.SubFilename = '' then
  1072. result := FileExistsUTF8(exFilename.Filename)
  1073. else
  1074. begin
  1075. SetCurrentMultiFile(exFilename.Filename);
  1076. ext := ExtractFileExt(exFilename.SubFilename);
  1077. if (length(ext)>0) and (ext[1]='.') then delete(ext,1,1);
  1078. result := CurrentMultiFile.IndexOf(ChangeFileExt(exFilename.SubFilename,''),ext)<>-1;
  1079. end;
  1080. end;
  1081. procedure TFileManager.DeleteFile(AFilenameUTF8: string);
  1082. var exFilename: TExtendedFilename;
  1083. ext: string;
  1084. index: integer;
  1085. begin
  1086. exFilename := ParseExtendedFilename(AFilenameUTF8);
  1087. if exFilename.SubFilename = '' then
  1088. DeleteFileUTF8(exFilename.Filename)
  1089. else
  1090. begin
  1091. SetCurrentMultiFile(exFilename.Filename);
  1092. ext := ExtractFileExt(exFilename.SubFilename);
  1093. if (length(ext)>0) and (ext[1]='.') then delete(ext,1,1);
  1094. index := CurrentMultiFile.IndexOf(ChangeFileExt(exFilename.SubFilename,''),ext);
  1095. if index <> -1 then
  1096. begin
  1097. CurrentMultiFile.Delete(index);
  1098. CurrentMultiFile.SaveToFile(CurrentMultiFileName);
  1099. CurrentMultiFileAge:= FileAgeUTF8(CurrentMultiFileName);
  1100. end;
  1101. end;
  1102. end;
  1103. function TFileManager.GetValidFilename(ASuggested: string): string;
  1104. var
  1105. i: Integer;
  1106. begin
  1107. result := ASuggested;
  1108. for i := 1 to length(result) do
  1109. case result[i] of
  1110. '/','\',':','|': result[i] := '-';
  1111. '?','%','*': result[i] := '_';
  1112. '"': result[i] := '''';
  1113. '<': result[i] := '(';
  1114. '>': result[i] := ')';
  1115. end;
  1116. end;
  1117. function TFileManager.GetDefaultFilename(ADirectory: string): string;
  1118. var
  1119. nonameCounter: Integer;
  1120. foundFiles: TFileInfoList;
  1121. begin
  1122. result := rsNoName;
  1123. nonameCounter := 1;
  1124. foundFiles := TFileInfoList.Create;
  1125. repeat
  1126. foundFiles.Clear;
  1127. GetDirectoryElements(ADirectory, result+'.*', [otNonFolders], foundFiles);
  1128. if foundFiles.Count = 0 then exit;
  1129. inc(nonameCounter);
  1130. result := rsNoName+IntToStr(nonameCounter);
  1131. until nonameCounter > 999;
  1132. result := '?';
  1133. end;
  1134. initialization
  1135. FileManager := TFileManager.Create;
  1136. finalization
  1137. FileManager.Free;
  1138. end.