IEDriveInfo.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. unit IEDriveInfo;
  2. {==================================================================
  3. Component TDriveInfo / Version 2.6 / January 2000
  4. ====================================================
  5. Description:
  6. ============
  7. Central drive management class. Provides information about all
  8. installed drives, for example wether drive is valid, disk is inserted
  9. displayname or volume size.
  10. Author:
  11. =======
  12. (c) Ingo Eckel 1999
  13. Sodener Weg 38
  14. 65812 Bad Soden
  15. Germany
  16. For detailed documentation and history see the documentation in TDriveInfo.htm.
  17. {==================================================================}
  18. {Required compiler options:}
  19. {$A+,B-,X+,H+,P+}
  20. interface
  21. uses
  22. Windows, Registry, SysUtils, Classes, ComCtrls, ShellApi, ShlObj, CommCtrl, Forms,
  23. BaseUtils;
  24. const
  25. {Flags used by TDriveInfo.ReadDriveStatus and TDriveView.RefreshRootNodes:}
  26. dsValid = 0; {checks only whether drive is still valid}
  27. dsImageIndex = 1; {Fetch imageindex, if not allready fetched}
  28. dsSize = 2; {Fetch disk size and serialnumber}
  29. dsDisplayName = 4; {Fetch drives displayname}
  30. dsAll = dsImageIndex or dsSize or dsDisplayName;
  31. FirstDrive = 'A';
  32. FirstFixedDrive = 'C';
  33. LastDrive = 'Z';
  34. FirstSpecialFolder = CSIDL_DESKTOP;
  35. LastSpecialFolder = CSIDL_PRINTHOOD;
  36. type
  37. TDrive = Char;
  38. PDriveInfoRec = ^TDriveInfoRec;
  39. TDriveInfoRec = record
  40. PIDL : PItemIDList; {Fully qualyfied PIDL}
  41. Init : Boolean; {Drivestatus was updated once}
  42. Valid : Boolean; {Drivestatus is valid}
  43. DriveReady : Boolean; {Drive is ready}
  44. DriveType : Integer; {DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE}
  45. DisplayName : string; {Windows displayname}
  46. PrettyName : string; {Prettyfied displayname}
  47. DriveSerial : DWORD; {Serial number of the drive}
  48. Size : Int64; {Drivesize}
  49. ImageIndex : Integer; {Drive imageIndex}
  50. end;
  51. TSpecialFolder = FirstSpecialFolder..LastSpecialFolder;
  52. PSpecialFolderRec = ^TSpecialFolderRec;
  53. TSpecialFolderRec = record
  54. Valid: Boolean;
  55. Location: string;
  56. DisplayName: string;
  57. ImageIndex: Integer;
  58. PIDL: PItemIDList;
  59. end;
  60. TDriveInfo = class(TObject)
  61. private
  62. FData: array[FirstDrive..LastDrive] of TDriveInfoRec;
  63. FNoDrives: DWORD;
  64. FDesktop: IShellFolder;
  65. FFolders: array[TSpecialFolder] of TSpecialFolderRec;
  66. FHonorDrivePolicy: Boolean;
  67. function GetData(Drive: TDrive): PDriveInfoRec;
  68. function GetFolder(Folder: TSpecialFolder): PSpecialFolderRec;
  69. procedure ReadDriveBasicStatus(Drive: TDrive);
  70. procedure ResetDrive(Drive: TDrive);
  71. procedure SetHonorDrivePolicy(Value: Boolean);
  72. public
  73. property Data[Drive: TDrive]: PDriveInfoRec read GetData; default;
  74. property SpecialFolder[Folder: TSpecialFolder]: PSpecialFolderRec read GetFolder;
  75. function GetImageIndex(Drive: TDrive): Integer;
  76. function GetDisplayName(Drive: TDrive): string;
  77. function GetPrettyName(Drive: TDrive): string;
  78. function ReadDriveStatus(Drive: TDrive; Flags: Integer): Boolean;
  79. property HonorDrivePolicy: Boolean read FHonorDrivePolicy write SetHonorDrivePolicy;
  80. constructor Create;
  81. destructor Destroy; override;
  82. procedure Load;
  83. end;
  84. function GetShellFileName(const Name: string): string; overload;
  85. function GetShellFileName(PIDL: PItemIDList): string; overLoad;
  86. function GetNetWorkName(Drive: Char): string;
  87. function GetNetWorkConnected(Drive: Char): Boolean;
  88. {Central drive information object instance of TDriveInfo}
  89. var
  90. DriveInfo : TDriveInfo;
  91. resourceString
  92. ErrorInvalidDrive = '%s is a invalid drive letter.';
  93. implementation
  94. uses
  95. Math, PIDL, OperationWithTimeout;
  96. constructor TDriveInfo.Create;
  97. begin
  98. inherited;
  99. FHonorDrivePolicy := True;
  100. Load;
  101. end; {TDriveInfo.Create}
  102. destructor TDriveInfo.Destroy;
  103. var
  104. Drive: TDrive;
  105. begin
  106. for Drive := FirstDrive to LastDrive do
  107. with FData[Drive] do
  108. begin
  109. SetLength(DisplayName, 0);
  110. SetLength(PrettyName, 0);
  111. // This causes access violation
  112. // FreePIDL(PIDL);
  113. end;
  114. inherited;
  115. end; {TDriveInfo.Destroy}
  116. function TDriveInfo.GetFolder(Folder: TSpecialFolder): PSpecialFolderRec;
  117. var
  118. FileInfo: TShFileInfo;
  119. Path: PChar;
  120. Flags: Word;
  121. begin
  122. Assert((Folder >= Low(FFolders)) and (Folder <= High(FFolders)));
  123. with FFolders[Folder] do
  124. begin
  125. if not Valid then
  126. begin
  127. SpecialFolderLocation(Folder, Location, PIDL);
  128. if Assigned(PIDL) then
  129. begin
  130. Path := PChar(PIDL);
  131. Flags := SHGFI_PIDL;
  132. end
  133. else
  134. begin
  135. Path := PChar(Location);
  136. Flags := 0;
  137. end;
  138. SHGetFileInfo(Path, 0, FileInfo, SizeOf(FileInfo),
  139. SHGFI_DISPLAYNAME or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or Flags);
  140. ImageIndex := FileInfo.iIcon;
  141. DisplayName := FileInfo.szDisplayName;
  142. Valid := True;
  143. end;
  144. end;
  145. Result := @FFolders[Folder];
  146. end;
  147. procedure TDriveInfo.SetHonorDrivePolicy(Value: Boolean);
  148. var
  149. Drive: TDrive;
  150. begin
  151. if HonorDrivePolicy <> Value then
  152. begin
  153. FHonorDrivePolicy := Value;
  154. for Drive := FirstDrive to LastDrive do
  155. begin
  156. ReadDriveBasicStatus(Drive);
  157. end;
  158. end;
  159. end;
  160. procedure TDriveInfo.ReadDriveBasicStatus(Drive: TDrive);
  161. begin
  162. with FData[Drive] do
  163. begin
  164. DriveType := Windows.GetDriveType(PChar(Drive + ':\'));
  165. Valid :=
  166. ((not FHonorDrivePolicy) or (not Bool((1 shl (Ord(Drive) - 65)) and FNoDrives))) and
  167. (DriveType in [DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_CDROM, DRIVE_RAMDISK, DRIVE_REMOTE]);
  168. end;
  169. end;
  170. procedure TDriveInfo.ResetDrive(Drive: TDrive);
  171. begin
  172. with FData[Drive] do
  173. begin
  174. DriveReady := False;
  175. DisplayName := '';
  176. PrettyName := '';
  177. DriveSerial := 0;
  178. Size := -1;
  179. ImageIndex := 0;
  180. end;
  181. end;
  182. procedure TDriveInfo.Load;
  183. var
  184. Drive: TDrive;
  185. Reg: TRegistry;
  186. Folder: TSpecialFolder;
  187. begin
  188. FNoDrives := 0;
  189. Reg := TRegistry.Create;
  190. try
  191. if Reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') Then
  192. Reg.ReadBinaryData('NoDrives', FNoDrives, SizeOf(FNoDrives));
  193. except
  194. try
  195. FNoDrives := Reg.ReadInteger('NoDrives');
  196. except
  197. end;
  198. end;
  199. Reg.Free;
  200. FDesktop := nil;
  201. for Drive := FirstDrive to LastDrive do
  202. begin
  203. with FData[Drive] do
  204. begin
  205. ReadDriveBasicStatus(Drive);
  206. Init := False;
  207. PIDL := nil;
  208. ResetDrive(Drive);
  209. end;
  210. end;
  211. for Folder := Low(FFolders) to High(FFolders) do
  212. FFolders[Folder].Valid := False;
  213. end;
  214. function TDriveInfo.GetImageIndex(Drive: TDrive): Integer;
  215. begin
  216. if (Drive < FirstDrive) or (Drive > LastDrive) then
  217. raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive]));
  218. Result := 0;
  219. if FData[Drive].Valid then
  220. begin
  221. if FData[Drive].ImageIndex = 0 then
  222. ReadDriveStatus(Drive, dsImageIndex);
  223. Result := FData[Drive].ImageIndex;
  224. end;
  225. end; {TDriveInfo.GetImageIndex}
  226. function TDriveInfo.GetDisplayName(Drive: TDrive): string;
  227. begin
  228. if (Drive < FirstDrive) or (Drive > LastDrive) then
  229. raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive]));
  230. Result := Drive + ':';
  231. if FData[Drive].Valid then
  232. begin
  233. if Length(FData[Drive].DisplayName) = 0 then
  234. ReadDriveStatus(Drive, dsDisplayName);
  235. Result := FData[Drive].DisplayName;
  236. end;
  237. end; {TDriveInfo.GetDisplayname}
  238. function TDriveInfo.GetPrettyName(Drive: TDrive): string;
  239. begin
  240. if (Drive < FirstDrive) or (Drive > LastDrive) then
  241. raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive]));
  242. Result := Drive + ':';
  243. if FData[Drive].Valid then
  244. begin
  245. if Length(FData[Drive].PrettyName) = 0 then
  246. ReadDriveStatus(Drive, dsDisplayName);
  247. Result := FData[Drive].PrettyName;
  248. end;
  249. end; {TDriveInfo.GetPrettyName}
  250. function TDriveInfo.GetData(Drive: TDrive): PDriveInfoRec;
  251. begin
  252. if not CharInSet(Upcase(Drive), ['A'..'Z']) then
  253. raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive]));
  254. Result := @FData[Upcase(Drive)];
  255. end; {TDriveInfo.GetData}
  256. function TDriveInfo.ReadDriveStatus(Drive: TDrive; Flags: Integer): Boolean;
  257. var
  258. ErrorMode: Word;
  259. FileInfo: TShFileInfo;
  260. DriveID: string;
  261. CPos: Integer;
  262. Eaten: ULONG;
  263. ShAttr: ULONG;
  264. MaxFileNameLength: DWORD;
  265. FileSystemFlags: DWORD;
  266. begin
  267. if not Assigned(FDesktop) then
  268. SHGetDesktopFolder(FDesktop);
  269. Drive := Upcase(Drive);
  270. if (Drive < FirstDrive) or (Drive > LastDrive) then
  271. raise EConvertError.Create(Format(ErrorInvalidDrive, [Drive]));
  272. with FData[Drive] do
  273. begin
  274. Init := True;
  275. ReadDriveBasicStatus(Drive);
  276. if Valid then
  277. begin
  278. if (not Assigned(PIDL)) and (Drive >= FirstFixedDrive) then
  279. begin
  280. ShAttr := 0;
  281. if DriveType = DRIVE_REMOTE then
  282. begin
  283. ShellFolderParseDisplayNameWithTimeout(
  284. FDesktop, Application.Handle, nil, PChar(Drive + ':\'), Eaten, PIDL, ShAttr, 2 * MSecsPerSec);
  285. end
  286. else
  287. begin
  288. FDesktop.ParseDisplayName(Application.Handle, nil, PChar(Drive + ':\'), Eaten, PIDL, ShAttr);
  289. end;
  290. end;
  291. {Read driveStatus:}
  292. if (Flags and dsSize) <> 0 then
  293. begin
  294. { turn off critical errors }
  295. ErrorMode := SetErrorMode(SEM_FailCriticalErrors or SEM_NOOPENFILEERRORBOX);
  296. try
  297. { drive 1 = a, 2 = b, 3 = c, etc. }
  298. Size := DiskSize(Ord(Drive) - $40);
  299. DriveReady := (Size >= 0);
  300. if DriveReady then
  301. begin
  302. {Access the physical drive:}
  303. if GetVolumeInformation(PChar(Drive + ':\'), nil, 0,
  304. @DriveSerial, MaxFileNameLength, FileSystemFlags,
  305. nil, 0) then
  306. begin
  307. end
  308. else
  309. begin
  310. DriveSerial := 0;
  311. end;
  312. end
  313. else
  314. begin
  315. DriveSerial := 0;
  316. end;
  317. finally
  318. { restore old error mode }
  319. SetErrorMode(ErrorMode);
  320. end;
  321. end;
  322. {DisplayName:}
  323. if (Flags and dsDisplayName <> 0) then
  324. begin
  325. {Fetch drives displayname:}
  326. if Assigned(PIDL) then DisplayName := GetShellFileName(PIDL)
  327. else
  328. if Drive < FirstFixedDrive then DisplayName := GetShellFileName(Drive + ':\')
  329. // typical reason we do not have PIDL is that it took too long to
  330. // call ParseDisplayName, in what case calling SHGetFileInfo with
  331. // path (instead of PIDL) will take long too, avoiding that and using
  332. // fallback
  333. else DisplayName := '(' + Drive + ':)';
  334. if DriveType <> DRIVE_REMOTE then
  335. begin
  336. PrettyName := Drive + ': ' + DisplayName;
  337. CPos := Pos(' (' + Drive + ':)', PrettyName);
  338. if CPos > 0 then
  339. Delete(PrettyName, CPos, 5);
  340. end
  341. else
  342. begin
  343. DriveID := GetNetWorkName(Drive);
  344. PrettyName := Format('%s: %s (%s)', [Drive, ExtractFileName(DriveID), ExtractFileDir(DriveID)]);
  345. end;
  346. end;
  347. {ImageIndex:}
  348. if ((Flags and dsImageIndex) <> 0) and (ImageIndex < 5) then
  349. begin
  350. if Assigned(PIDL) then
  351. begin
  352. SHGetFileInfo(PChar(PIDL), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_PIDL)
  353. end
  354. else
  355. begin
  356. SHGetFileInfo(PChar(Drive + ':\'), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  357. end;
  358. ImageIndex := FileInfo.iIcon;
  359. end;
  360. end
  361. else
  362. begin
  363. if Assigned(PIDL) then
  364. FreePIDL(PIDL);
  365. ResetDrive(Drive);
  366. end;
  367. Result := Valid and DriveReady;
  368. end;
  369. end; {TDriveInfo.ReadDriveStatus}
  370. function GetShellFileName(const Name: string): string;
  371. var
  372. SFI: TSHFileInfo;
  373. E: Integer;
  374. begin
  375. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  376. try
  377. if SHGetFileInfo(PChar(Name), 0, SFI, SizeOf(TSHFileInfo), SHGFI_DISPLAYNAME) <> 0 then
  378. Result := SFI.szDisplayName;
  379. finally
  380. SetErrorMode(E);
  381. end;
  382. end; {GetShellFileName}
  383. function GetShellFileName(PIDL: PItemIDList): string;
  384. var
  385. SFI: TSHFileInfo;
  386. E: Integer;
  387. begin
  388. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  389. try
  390. if SHGetFileInfo(PChar(PIDL), 0, SFI, SizeOf(TSHFileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME) <> 0 then
  391. Result := SFI.szDisplayName;
  392. finally
  393. SetErrorMode(E);
  394. end;
  395. end; {GetShellFileName}
  396. function GetNetWorkName(Drive: Char): string;
  397. var
  398. P: array[0..MAX_PATH] of Char;
  399. MaxLen : DWORD;
  400. begin
  401. MaxLen := MAX_PATH;
  402. if WNetGetConnection(PChar(string(Drive + ':')), P, MaxLen) = NO_ERROR then
  403. Result := P
  404. else
  405. Result := '';
  406. end; {GetNetWorkName}
  407. type
  408. LPBYTE = ^BYTE;
  409. LMSTR = LPWSTR;
  410. NET_API_STATUS = DWORD;
  411. _USE_INFO_1 = record
  412. ui1_local: LMSTR;
  413. ui1_remote: LMSTR;
  414. ui1_password: LMSTR;
  415. ui1_status: DWORD;
  416. ui1_asg_type: DWORD;
  417. ui1_refcount: DWORD;
  418. ui1_usecount: DWORD;
  419. end;
  420. USE_INFO_1 = _USE_INFO_1;
  421. PUSE_INFO_1 = ^USE_INFO_1;
  422. LPVOID = Pointer;
  423. const
  424. USE_OK = 0;
  425. USE_PAUSED = 1;
  426. USE_SESSLOST = 2;
  427. USE_DISCONN = 2;
  428. USE_NETERR = 3;
  429. USE_CONN = 4;
  430. USE_RECONN = 5;
  431. function NetUseGetInfo(UncServerName: LMSTR; UseName: LMSTR; Level: DWORD; var BufPtr: LPBYTE): NET_API_STATUS; stdcall; external 'netapi32.dll';
  432. function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall; external 'netapi32.dll';
  433. function GetNetWorkConnected(Drive: Char): Boolean;
  434. var
  435. BufPtr: LPBYTE;
  436. NetResult: Integer;
  437. begin
  438. NetResult := NetUseGetInfo(nil, PChar(Drive + ':'), 1, BufPtr);
  439. if NetResult = 0 then
  440. begin
  441. Result := (PUSE_INFO_1(BufPtr)^.ui1_status = USE_OK);
  442. NetApiBufferFree(LPVOID(BufPtr));
  443. end
  444. else
  445. begin
  446. // NetUseGetInfo works for DFS shares only, hence when it fails
  447. // we suppose different share type and fallback to "connected"
  448. Result := True;
  449. end;
  450. end;
  451. initialization
  452. if not Assigned(DriveInfo) then
  453. DriveInfo := TDriveInfo.Create;
  454. finalization
  455. if Assigned(DriveInfo) then
  456. begin
  457. DriveInfo.Free;
  458. DriveInfo := nil;
  459. end;
  460. end.