IEDriveInfo.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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;
  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. if DriveType = DRIVE_REMOTE then
  281. begin
  282. ParseDisplayNameWithTimeout(FDesktop, Drive + ':\', PIDL);
  283. end
  284. else
  285. begin
  286. ShAttr := 0;
  287. FDesktop.ParseDisplayName(Application.Handle, nil, PChar(Drive + ':\'), Eaten, PIDL, ShAttr);
  288. end;
  289. end;
  290. {Read driveStatus:}
  291. if (Flags and dsSize) <> 0 then
  292. begin
  293. { turn off critical errors }
  294. ErrorMode := SetErrorMode(SEM_FailCriticalErrors or SEM_NOOPENFILEERRORBOX);
  295. try
  296. { drive 1 = a, 2 = b, 3 = c, etc. }
  297. Size := DiskSize(Ord(Drive) - $40);
  298. DriveReady := (Size >= 0);
  299. if DriveReady then
  300. begin
  301. {Access the physical drive:}
  302. if GetVolumeInformation(PChar(Drive + ':\'), nil, 0,
  303. @DriveSerial, MaxFileNameLength, FileSystemFlags,
  304. nil, 0) then
  305. begin
  306. end
  307. else
  308. begin
  309. DriveSerial := 0;
  310. end;
  311. end
  312. else
  313. begin
  314. DriveSerial := 0;
  315. end;
  316. finally
  317. { restore old error mode }
  318. SetErrorMode(ErrorMode);
  319. end;
  320. end;
  321. {DisplayName:}
  322. if (Flags and dsDisplayName <> 0) then
  323. begin
  324. {Fetch drives displayname:}
  325. if Assigned(PIDL) then DisplayName := GetShellFileName(PIDL)
  326. else
  327. if Drive < FirstFixedDrive then DisplayName := GetShellFileName(Drive + ':\')
  328. // typical reason we do not have PIDL is that it took too long to
  329. // call ParseDisplayName, in what case calling SHGetFileInfo with
  330. // path (instead of PIDL) will take long too, avoiding that and using
  331. // fallback
  332. else DisplayName := '(' + Drive + ':)';
  333. if DriveType <> DRIVE_REMOTE then
  334. begin
  335. PrettyName := Drive + ': ' + DisplayName;
  336. CPos := Pos(' (' + Drive + ':)', PrettyName);
  337. if CPos > 0 then
  338. Delete(PrettyName, CPos, 5);
  339. end
  340. else
  341. begin
  342. DriveID := GetNetWorkName(Drive);
  343. PrettyName := Drive + ': ' + ExtractFileName(DriveID);
  344. end;
  345. end;
  346. {ImageIndex:}
  347. if ((Flags and dsImageIndex) <> 0) and (ImageIndex < 5) then
  348. begin
  349. if Assigned(PIDL) then
  350. begin
  351. SHGetFileInfo(PChar(PIDL), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_PIDL)
  352. end
  353. else
  354. begin
  355. SHGetFileInfo(PChar(Drive + ':\'), 0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  356. end;
  357. ImageIndex := FileInfo.iIcon;
  358. end;
  359. end
  360. else
  361. begin
  362. if Assigned(PIDL) then
  363. FreePIDL(PIDL);
  364. ResetDrive(Drive);
  365. end;
  366. Result := Valid and DriveReady;
  367. end;
  368. end; {TDriveInfo.ReadDriveStatus}
  369. function GetShellFileName(const Name: string): string;
  370. var
  371. SFI: TSHFileInfo;
  372. E: Integer;
  373. begin
  374. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  375. try
  376. if SHGetFileInfo(PChar(Name), 0, SFI, SizeOf(TSHFileInfo), SHGFI_DISPLAYNAME) <> 0 then
  377. Result := SFI.szDisplayName;
  378. finally
  379. SetErrorMode(E);
  380. end;
  381. end; {GetShellFileName}
  382. function GetShellFileName(PIDL: PItemIDList): string;
  383. var
  384. SFI: TSHFileInfo;
  385. E: Integer;
  386. begin
  387. E := SetErrorMode(SEM_FAILCRITICALERRORS);
  388. try
  389. if SHGetFileInfo(PChar(PIDL), 0, SFI, SizeOf(TSHFileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME) <> 0 then
  390. Result := SFI.szDisplayName;
  391. finally
  392. SetErrorMode(E);
  393. end;
  394. end; {GetShellFileName}
  395. function GetNetWorkName(Drive: Char): string;
  396. var
  397. P: array[0..MAX_PATH] of Char;
  398. MaxLen : DWORD;
  399. begin
  400. MaxLen := MAX_PATH;
  401. if WNetGetConnection(PChar(string(Drive + ':')), P, MaxLen) = NO_ERROR then
  402. Result := P
  403. else
  404. Result := '';
  405. end; {GetNetWorkName}
  406. type
  407. LPBYTE = ^BYTE;
  408. LMSTR = LPWSTR;
  409. NET_API_STATUS = DWORD;
  410. _USE_INFO_1 = record
  411. ui1_local: LMSTR;
  412. ui1_remote: LMSTR;
  413. ui1_password: LMSTR;
  414. ui1_status: DWORD;
  415. ui1_asg_type: DWORD;
  416. ui1_refcount: DWORD;
  417. ui1_usecount: DWORD;
  418. end;
  419. USE_INFO_1 = _USE_INFO_1;
  420. PUSE_INFO_1 = ^USE_INFO_1;
  421. LPVOID = Pointer;
  422. const
  423. USE_OK = 0;
  424. USE_PAUSED = 1;
  425. USE_SESSLOST = 2;
  426. USE_DISCONN = 2;
  427. USE_NETERR = 3;
  428. USE_CONN = 4;
  429. USE_RECONN = 5;
  430. function NetUseGetInfo(UncServerName: LMSTR; UseName: LMSTR; Level: DWORD; var BufPtr: LPBYTE): NET_API_STATUS; stdcall; external 'netapi32.dll';
  431. function NetApiBufferFree(Buffer: Pointer): DWORD; stdcall; external 'netapi32.dll';
  432. function GetNetWorkConnected(Drive: Char): Boolean;
  433. var
  434. BufPtr: LPBYTE;
  435. NetResult: Integer;
  436. begin
  437. NetResult := NetUseGetInfo(nil, PChar(Drive + ':'), 1, BufPtr);
  438. if NetResult = 0 then
  439. begin
  440. Result := (PUSE_INFO_1(BufPtr)^.ui1_status = USE_OK);
  441. NetApiBufferFree(LPVOID(BufPtr));
  442. end
  443. else
  444. begin
  445. // NetUseGetInfo works for DFS shares only, hence when it fails
  446. // we suppose different share type and fallback to "connected"
  447. Result := True;
  448. end;
  449. end;
  450. initialization
  451. if not Assigned(DriveInfo) then
  452. DriveInfo := TDriveInfo.Create;
  453. finalization
  454. if Assigned(DriveInfo) then
  455. begin
  456. DriveInfo.Free;
  457. DriveInfo := nil;
  458. end;
  459. end.