Subversion Repositories recyclebinunit

Rev

Rev 89 | Rev 93 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
75 daniel-mar 1
unit RecBinUnit2 platform;
2
 
3
////////////////////////////////////////////////////////////////////////////////////
76 daniel-mar 4
// RECYCLE-BIN-UNIT V2 BY DANIEL MARSCHALL, VIATHINKSOFT                          //
75 daniel-mar 5
// E-MAIL: info@daniel-marschall.de                                               //
76 daniel-mar 6
// Web:    www.daniel-marschall.de & www.viathinksoft.de                          //
75 daniel-mar 7
////////////////////////////////////////////////////////////////////////////////////
82 daniel-mar 8
// Revision: 30 JUN 2022                                                          //
75 daniel-mar 9
// This unit is freeware, but please link to my website if you are using it!      //
10
////////////////////////////////////////////////////////////////////////////////////
11
// Successfully tested with:                                                      //
12
// Windows 95b (without IE4 Shell Extensions)                                     //
13
// Windows 95b (with IE4 Shell Extensions)                                        //
76 daniel-mar 14
// Windows 98 SE                                                                  //
75 daniel-mar 15
// Windows NT4 SP6                                                                //
76 daniel-mar 16
// Windows XP SP3                                                                 //
17
// Windows 2000 SP4                                                               //
75 daniel-mar 18
// Windows 2003 Server EE SP1                                                     //
19
// Windows Vista                                                                  //
20
// Windows 7                                                                      //
82 daniel-mar 21
// Windows 10 (version 1 and version 2 format)                                    //
22
// Windows 11                                                                     //
75 daniel-mar 23
////////////////////////////////////////////////////////////////////////////////////
24
 
25
// Delphi 7 Compatibility:  (TODO: compiler switches)
26
// - Remove "static"
27
// - Remove "strict"
28
// - Remove "$REGION"
29
 
30
// TODO: ReadBuffer überall try-except
31
// TODO: Always check EOF before reading anything?
76 daniel-mar 32
// TODO: Don't crash when timestamp is invalid. Do something else instead.
75 daniel-mar 33
// TODO: Is it possible to identify a Vista-file that is not named $Ixxxxxx.ext?
34
// TODO: RecyclerGetInfofiles() check additionally for removable device?
35
//       RecyclerIsValid() is false.
36
// TODO: Make it possible to empty the recycle bin of one specific drive!
37
// TODO: Unknown! Do you know the answer?
38
//       - How does Windows 9x/NT manage the daylight saving time (if it does)?
39
//       - How does Windows Vista+ react to a RECYCLER\ folder on a NTFS device?
40
//       - How does Windows Vista+ react to a RECYCLED\ folder on a FAT device? ==> Win7: is ignored!
41
//       - How does Windows XP react to RECYCLED\ folder on a FAT device?
42
// TODO: Translate all comments from German to English
78 daniel-mar 43
// TODO: Do we need this (maybe not all drives have A: till Z:?) http://stackoverflow.com/questions/17110543/how-to-retrieve-the-disk-signature-of-all-the-disks-in-windows-using-delphi-7
44
// TODO: Add a lot of setters for system config stuff
75 daniel-mar 45
 
46
// If enabled, the deletion timestamps will not be converted by the WinAPI.
47
{.$DEFINE FILETIME_DELPHI_CODE}
48
 
49
// If a value is set in HKEY_LOCAL_MACHINE, it will be prefered, even if gpedit.msc shows "Not configured"!
50
{$DEFINE GroupPolicyAcceptHKLMTrick}
51
 
52
interface
53
 
54
uses
78 daniel-mar 55
  Windows, SysUtils, Classes, ContNrs, ShellAPI, Registry, Messages, Math;
75 daniel-mar 56
 
57
const
82 daniel-mar 58
  RECBINUNIT_VERSION = '2022-06-30';
75 daniel-mar 59
 
60
  RECYCLER_CLSID: TGUID = '{645FF040-5081-101B-9F08-00AA002F954E}';
78 daniel-mar 61
  NULL_GUID:      TGUID = '{00000000-0000-0000-0000-000000000000}';
75 daniel-mar 62
 
63
type
64
  EAPICallError = class(Exception);
65
  EEventCategoryNotDefined = class(Exception);
66
  EInvalidDrive = class(Exception);
67
 
68
  PSHQueryRBInfo = ^TSHQueryRBInfo;
69
  TSHQueryRBInfo = packed record
76 daniel-mar 70
    cbSize      : DWORD;
75 daniel-mar 71
    i64Size     : int64;
72
    i64NumItems : int64;
73
  end;
74
 
75
  TRbRecycleBinItem = class(TObject)
76
  strict private
77
    function GetSource: string;
78
  strict protected
79
    FSourceAnsi: AnsiString;
80
    FSourceUnicode: WideString;
81
    FID: string;
82
    FSourceDrive: Char;
83
    FDeletionTime: TDateTime;
84
    FOriginalSize: int64;
85
    FIndexFile: string;
86
    FRemovedEntry: boolean;
87
    procedure ReadFromStream(stream: TStream); virtual; abstract;
88
    function GetPhysicalFile: string; virtual; abstract; // protected, because it will be read by "property"
89
  public
90
    property PhysicalFile: string read GetPhysicalFile;
91
    property SourceAnsi: AnsiString read FSourceAnsi;
92
    property SourceUnicode: WideString read FSourceUnicode;
86 daniel-mar 93
    property Source: string read GetSource; // will bei either ANSI or Unicode, depending on the Delphi version
75 daniel-mar 94
    property ID: string read FID;
95
    property SourceDrive: Char read FSourceDrive;
96
    property DeletionTime: TDateTime read FDeletionTime;
97
    property OriginalSize: int64 read FOriginalSize;
98
    property IndexFile: string read FIndexFile;
99
    property RemovedEntry: boolean read FRemovedEntry; // the file is NOT in the recycle bin anymore!
100
 
88 daniel-mar 101
    // Attention: There are no official API calls. The delete and recover
75 daniel-mar 102
    // functions might fail and/or damage the shell cache. Handle with care!
103
    function DeleteFile: boolean; virtual; abstract;
104
    function RecoverFile: boolean; virtual; abstract;
105
    function OpenFile: boolean; virtual; abstract;
106
  end;
107
 
76 daniel-mar 108
  TRbInfoAItem = class(TRbRecycleBinItem)
75 daniel-mar 109
  strict protected
110
    procedure ReadFromStream(stream: TStream); override;
111
    function GetPhysicalFile: string; override;
112
  public
113
    constructor Create(fs: TStream; AIndexFile: string);
114
    function DeleteFile: boolean; override;
115
    // TODO: function RecoverFile: boolean; override;
116
    // TODO: function OpenFile: boolean; override;
117
  end;
118
 
76 daniel-mar 119
  TRbInfoWItem = class(TRbRecycleBinItem)
75 daniel-mar 120
  strict protected
121
    procedure ReadFromStream(stream: TStream); override;
122
    function GetPhysicalFile: string; override;
123
  public
124
    constructor Create(fs: TStream; AIndexFile: string);
125
    function DeleteFile: boolean; override;
126
    // TODO: function RecoverFile: boolean; override;
127
    // TODO: function OpenFile: boolean; override;
128
  end;
129
 
130
  TRbVistaItem = class(TRbRecycleBinItem)
131
  strict protected
132
    procedure ReadFromStream(stream: TStream); override;
133
    function GetPhysicalFile: string; override;
134
  public
135
    constructor Create(fs: TStream; AIndexFile, AID: string);
136
    function DeleteFile: boolean; override;
137
    // TODO: function RecoverFile: boolean; override;
138
    // TODO: function OpenFile: boolean; override;
139
  end;
140
 
141
  TRbRecycleBin = class(TObject)
142
  strict private
143
    FFileOrDirectory: string;
144
    FSID: string;
76 daniel-mar 145
    FTolerantReading: boolean;
75 daniel-mar 146
  public
147
    constructor Create(AFileOrDirectory: string; ASID: string='');
148
 
149
    function GetItem(id: string): TRbRecycleBinItem;
150
    procedure ListItems(list: TObjectList{TRbRecycleBinItem});
151
    function CheckIndexes(slErrors: TStrings): boolean;
152
 
153
    property FileOrDirectory: string read FFileOrDirectory;
154
    property SID: string read FSID;
76 daniel-mar 155
 
156
    // Allows an index file to be read, even if an incompatible multiboot combination
157
    // corrupted it. Default: true.
158
    property TolerantReading: boolean read FTolerantReading write FTolerantReading;
75 daniel-mar 159
  end;
160
 
161
  // TODO: Wie sieht es aus mit Laufwerken, die nur als Mount-Point eingebunden sind?
162
  TRbDrive = class(TObject)
163
  strict private
89 daniel-mar 164
    FDriveLetter: AnsiChar;
75 daniel-mar 165
 
78 daniel-mar 166
    function OldCapacityPercent(var res: integer): boolean; // in % (0-100)
167
    function NewCapacityAbsolute(var res: integer): boolean; // in MB
168
 
169
    function DiskSize: integer; // in MB
170
    function DriveNumber: integer;
75 daniel-mar 171
  strict protected
172
    function IsFAT: boolean;
173
    procedure CheckDriveExisting;
78 daniel-mar 174
 
175
    // will return NULL_GUID in case of an error or if it is not supported
176
    function GetVolumeGUID: TGUID;
177
    function GetVolumeGUIDAvailable: boolean;
178
 
179
    // TODO: get drive serial
75 daniel-mar 180
  public
89 daniel-mar 181
    constructor Create(ADriveLetter: AnsiChar);
75 daniel-mar 182
 
183
    // Wenn UserSID='', dann werden alle Recycler gefunden
184
    procedure ListRecycleBins(list: TObjectList{TRbRecycleBin}; UserSID: string='');
185
 
89 daniel-mar 186
    property DriveLetter: AnsiChar read FDriveLetter;
75 daniel-mar 187
    property VolumeGUID: TGUID read GetVolumeGUID;
77 daniel-mar 188
    property VolumeGUIDAvailable: boolean read GetVolumeGUIDAvailable;
75 daniel-mar 189
    function GetAPIInfo: TSHQueryRBInfo;
190
    function GetSize: int64;
191
    function GetNumItems: int64;
192
    function IsEmpty: boolean;
193
 
78 daniel-mar 194
    function GetMaxPercentUsage: Extended; // 0..1
195
    function GetMaxAbsoluteUsage: integer; // in MB
75 daniel-mar 196
    function GetNukeOnDelete: boolean;
197
  end;
198
 
199
  GPOLICYBOOL = (gpUndefined, gpEnabled, gpDisabled);
200
 
201
  TRecycleBinManager = class(TObject)
202
  public
203
    class procedure ListDrives(list: TObjectList{TRbDrive}); static;
89 daniel-mar 204
    class function RecycleBinPossible(Drive: AnsiChar): boolean; static;
75 daniel-mar 205
 
206
    class function OwnRecyclersSize: int64; static;
207
    class function OwnRecyclersNumItems: int64; static;
208
    class function OwnRecyclersEmpty: boolean; static;
209
 
210
    class function EmptyOwnRecyclers(flags: cardinal): boolean; overload; static;
211
    class function EmptyOwnRecyclers(sound, progress, confirmation: boolean): boolean; overload; static;
212
 
213
    class function RecyclerGetCurrentIconString: string; static;
214
    class function RecyclerGetDefaultIconString: string; static;
215
    class function RecyclerGetEmptyIconString: string; static;
216
    class function RecyclerGetFullIconString: string; static;
217
 
218
    class function GetGlobalMaxPercentUsage: integer; static; // TODO: In Win Vista: absolute and not relative sizes
219
    class function GetGlobalNukeOnDelete: boolean; static;
220
    class function UsesGlobalSettings: boolean; static;
221
 
222
    class function RecyclerGetName: string; static;
223
    class function RecyclerGetInfoTip: string; static;
224
    class function RecyclerGetIntroText: string; static;
225
 
226
    class function RecyclerEmptyEventGetCurrentSound: string; static;
227
    class function RecyclerEmptyEventGetDefaultSound: string; static;
228
    class function RecyclerEmptyEventGetName: string; static;
229
    class function RecyclerEmptyEventGetSound(ACategory: string): string; static;
230
    class procedure RecyclerEmptyEventGetSoundCategories(AStringList: TStringList); static;
231
 
232
    // TODO: In future also detect for other users
233
    // TODO: Also make a setter (incl. Message to Windows Explorer?)
234
    class function RecyclerGroupPolicyConfirmFileDelete: GPOLICYBOOL; static;
235
    class function RecyclerGroupPolicyNoRecycleFiles: GPOLICYBOOL; static;
236
    class function RecyclerGroupPolicyRecycleBinSize: integer; static;
237
 
238
    class function RecyclerConfirmationDialogEnabled: boolean; static;
239
    class procedure RecyclerConfirmationDialogSetEnabled(NewSetting: boolean); static;
240
    class function RecyclerShellStateConfirmationDialogEnabled: boolean; static;
241
 
242
    // Diese Funktion ist false, wenn sie z.B. unter Windows 95 ohne Internet Explorer
243
    // 4.0 Shell Extension ausgeführt wird. Wenn abwärtskompatibler Code geschrieben
244
    // werden soll, sollte RecyclerQueryFunctionAvailable() verwendet werden, da
245
    // unter Windows 95 folgende Funktionalitäten NICHT vorhanden sind:
246
    // - RecyclerIsEmpty
247
    // - RecyclerGetNumItems
248
    // - RecyclerGetSize
249
    // - RecyclerGetAPIInfo
250
    class function RecyclerQueryFunctionAvailable: boolean; static;
251
 
252
    class function RecyclerAddFileOrFolder(FileOrFolder: string; confirmation: boolean=false): boolean; static;
253
  end;
254
 
255
function GPBoolToString(value: GPOLICYBOOL): string;
256
 
257
implementation
258
 
259
uses
260
  RecBinUnitLowLvl;
261
 
262
{$REGION 'WinAPI/RTL declarations'}
263
(*
264
const
265
  {$IFDEF MSWINDOWS}
266
    shell32  = 'shell32.dll';
267
    advapi32 = 'advapi32.dll';
268
    kernel32 = 'kernel32.dll';
269
  {$ENDIF}
270
  {$IFDEF LINUX}
271
    shell32  = 'libshell32.borland.so';
272
    advapi32 = 'libwine.borland.so';
273
    kernel32 = 'libwine.borland.so';
274
  {$ENDIF}
275
*)
276
 
277
type
278
  SHELLSTATE = record
279
    Flags1: DWORD;
280
(*
281
    BOOL fShowAllObjects : 1;
282
    BOOL fShowExtensions : 1;
283
    BOOL fNoConfirmRecycle : 1;
284
 
285
    BOOL fShowSysFiles : 1;
286
    BOOL fShowCompColor : 1;
287
    BOOL fDoubleClickInWebView : 1;
288
    BOOL fDesktopHTML : 1;
289
    BOOL fWin95Classic : 1;
290
    BOOL fDontPrettyPath : 1;
291
    BOOL fShowAttribCol : 1; // No longer used, dead bit
292
    BOOL fMapNetDrvBtn : 1;
293
    BOOL fShowInfoTip : 1;
294
    BOOL fHideIcons : 1;
295
    BOOL fWebView : 1;
296
    BOOL fFilter : 1;
297
    BOOL fShowSuperHidden : 1;
298
    BOOL fNoNetCrawling : 1;
299
*)
300
    dwWin95Unused: DWORD; // Win95 only - no longer supported pszHiddenFileExts
301
    uWin95Unused: UINT; // Win95 only - no longer supported cbHiddenFileExts
302
 
303
    // Note: Not a typo!  This is a persisted structure so we cannot use LPARAM
304
    lParamSort: Integer;
305
    iSortDirection: Integer;
306
 
307
    version: UINT;
308
 
309
    // new for win2k. need notUsed var to calc the right size of ie4 struct
310
    // FIELD_OFFSET does not work on bit fields
311
    uNotUsed: UINT; // feel free to rename and use
312
    Flags2: DWORD;
313
(*
314
    BOOL fSepProcess: 1;
315
    // new for Whistler.
316
    BOOL fStartPanelOn: 1;       //Indicates if the Whistler StartPanel mode is ON or OFF.
317
    BOOL fShowStartPage: 1;      //Indicates if the Whistler StartPage on desktop is ON or OFF.
318
    UINT fSpareFlags : 13;
319
*)
320
  end;
321
  LPSHELLSTATE = ^SHELLSTATE;
322
 
323
const
324
  // Masks for the SHELLSTATE
325
  SSF_SHOWALLOBJECTS       = $00000001;
326
  SSF_SHOWEXTENSIONS       = $00000002;
327
  SSF_HIDDENFILEEXTS       = $00000004;
328
  SSF_SERVERADMINUI        = $00000004;
329
  SSF_SHOWCOMPCOLOR        = $00000008;
330
  SSF_SORTCOLUMNS          = $00000010;
331
  SSF_SHOWSYSFILES         = $00000020;
332
  SSF_DOUBLECLICKINWEBVIEW = $00000080;
333
  SSF_SHOWATTRIBCOL        = $00000100;
334
  SSF_DESKTOPHTML          = $00000200;
335
  SSF_WIN95CLASSIC         = $00000400;
336
  SSF_DONTPRETTYPATH       = $00000800;
337
  SSF_SHOWINFOTIP          = $00002000;
338
  SSF_MAPNETDRVBUTTON      = $00001000;
339
  SSF_NOCONFIRMRECYCLE     = $00008000;
340
  SSF_HIDEICONS            = $00004000;
341
  SSF_FILTER               = $00010000;
342
  SSF_WEBVIEW              = $00020000;
343
  SSF_SHOWSUPERHIDDEN      = $00040000;
344
  SSF_SEPPROCESS           = $00080000;
345
  SSF_NONETCRAWLING        = $00100000;
346
  SSF_STARTPANELON         = $00200000;
347
  SSF_SHOWSTARTPAGE        = $00400000;
348
{$ENDREGION}
349
 
350
resourcestring
351
  LNG_API_CALL_ERROR = 'Error while calling the API. Additional information: "%s".';
352
  LNG_NOT_CALLABLE = '%s not callable';
353
  LNG_ERROR_CODE = '%s (Arguments: %s) returns error code %s';
354
  LNG_FILE_NOT_FOUND = 'File not found: %s';
76 daniel-mar 355
  LNG_INVALID_INFO_FORMAT = 'Unexpected record size: %s';
75 daniel-mar 356
  LNG_DRIVE_NOT_EXISTING = 'Drive %s does not exist.';
357
 
358
const
359
  C_SHQueryRecycleBin = 'SHQueryRecycleBinA';
360
  C_GetVolumeNameForVolumeMountPoint = 'GetVolumeNameForVolumeMountPointA';
361
  C_SHEmptyRecycleBinA = 'SHEmptyRecycleBinA';
362
  C_SHGetSettings = 'SHGetSettings';
363
  C_SHGetSetSettings = 'SHGetSetSettings';
364
 
365
type
366
  TSHQueryRecycleBin = function(pszRootPath: LPCTSTR; var pSHQueryRBInfo: TSHQueryRBInfo): HRESULT; stdcall;
89 daniel-mar 367
  TGetVolumeNameForVolumeMountPointA = function(lpszVolumeMountPoint: LPCSTR; lpszVolumeName: LPSTR; cchBufferLength: DWORD): BOOL; stdcall;
75 daniel-mar 368
  TSHEmptyRecycleBin = function(Wnd: HWND; pszRootPath: PChar; dwFlags: DWORD): HRESULT; stdcall;
82 daniel-mar 369
  TSHGetSettings = procedure(var lpss: SHELLSTATE; dwMask: DWORD); stdcall;
370
  TSHGetSetSettings = procedure(var lpss: SHELLSTATE; dwMask: DWORD; bSet: BOOL); stdcall;
75 daniel-mar 371
 
90 daniel-mar 372
function WideCharArrayToWideString(x: array of WideChar): WideString;
373
var
374
  i: integer;
375
begin
376
  // In x86, the "cast" works with WideString.
377
  // In x64, it does not work (outputs empty string, without compiler warning!)
378
  // So, we created this fake-cast
379
  SetLength(result, Length(x));
380
  for i := 0 to Length(x)-1 do
381
    result[i+1] := x[i];
382
end;
383
 
384
function AnsiCharArrayToWideString(x: array of AnsiChar): WideString;
385
var
386
  i: integer;
387
begin
388
  SetLength(result, Length(x));
389
  for i := 0 to Length(x)-1 do
390
    result[i+1] := WideChar(x[i]);
391
end;
392
 
88 daniel-mar 393
procedure AnsiRemoveNulChars(var s: AnsiString);
394
begin
395
  while (Length(s) > 0) and (s[Length(s)] = #0) do
396
    s := Copy(s, 1, Length(s)-1);
397
end;
398
 
399
procedure UnicodeRemoveNulChars(var s: WideString);
400
begin
401
  while (Length(s) > 0) and (s[Length(s)] = #0) do
402
    s := Copy(s, 1, Length(s)-1);
403
end;
404
 
89 daniel-mar 405
function GetDriveGUID(driveLetter: AnsiChar; var guid: TGUID): DWORD;
75 daniel-mar 406
var
407
  Buffer: array[0..50] of AnsiChar;
408
  x: string;
89 daniel-mar 409
  PGetVolumeNameForVolumeMountPointA: TGetVolumeNameForVolumeMountPointA;
75 daniel-mar 410
  RBHandle: THandle;
411
begin
412
  RBHandle := LoadLibrary(kernel32);
413
  try
414
    if RBHandle <> 0 then
415
    begin
89 daniel-mar 416
      PGetVolumeNameForVolumeMountPointA := GetProcAddress(RBHandle, C_GetVolumeNameForVolumeMountPoint);
417
      if not Assigned(@PGetVolumeNameForVolumeMountPointA) then
75 daniel-mar 418
      begin
419
        result := GetLastError;
420
        FreeLibrary(RBHandle);
421
        RBHandle := 0;
422
      end
423
      else
424
      begin
89 daniel-mar 425
        if PGetVolumeNameForVolumeMountPointA(PAnsiChar(AnsiString(driveLetter+':\')), Buffer, SizeOf(Buffer)) then
75 daniel-mar 426
        begin
89 daniel-mar 427
          x := string(buffer);
75 daniel-mar 428
          x := copy(x, 11, 38);
429
          guid := StringToGUID(x);
430
          result := ERROR_SUCCESS;
431
        end
432
        else
433
          result := GetLastError;
434
      end;
435
    end
436
    else result := GetLastError;
437
  finally
438
    if RBHandle <> 0 then FreeLibrary(RBHandle);
439
  end;
440
end;
441
 
442
function FileTimeToDateTime(FileTime: FILETIME): TDateTime;
443
{$IFDEF FILETIME_DELPHI_CODE}
444
var
445
  SystemTime: TSystemTime;
446
  nowUTC: TDateTime;
447
  gmtDifference: int64;
448
begin
449
  GetSystemTime(SystemTime);
450
  with SystemTime do
451
  begin
452
    // http://www.delphipraxis.net/post340194.html#34019
453
    nowUTC := EncodeDate(wYear, wMonth, wDay) +
454
              EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
455
  end;
456
 
457
  gmtDifference := datetimetounix(nowUTC) - datetimetounix(Now);
458
 
459
  // http://www.e-fense.com/helix/Docs/Recycler_Bin_Record_Reconstruction.pdf states:
460
  // UnixTime = 0.0000001 * NTTime + 11644473600
461
  // This is wrong! The correct formula is:
462
  // UnixTime = 0.0000001 * NTTime - 11644473600 + c * 3600
463
  // c = GMT-Difference (MEZ = 1) inclusive daylight saving time (+3600 seconds)
464
  result := unixtodatetime(round(0.0000001 * int64(FileTime)) - 11644473600 - gmtDifference);
465
{$ELSE}
466
var
467
  LocalTime: TFileTime;
468
  DOSTime: Integer;
469
begin
470
  FileTimeToLocalFileTime(FileTime, LocalTime);
471
  FileTimeToDosDateTime(LocalTime, LongRec(DOSTime).Hi, LongRec(DOSTime).Lo);
472
  Result := FileDateToDateTime(DOSTime);
473
{$ENDIF}
474
end;
475
 
476
function DeleteDirectory(const Name: string): boolean;
477
var
478
  F: TSearchRec;
479
begin
480
  result := true;
481
  if FindFirst(IncludeTrailingPathDelimiter(Name) + '*', faAnyFile, F) = 0 then
482
  begin
483
    try
484
      repeat
485
        if F.Attr and faDirectory <> 0 then
486
        begin
487
          if (F.Name <> '.') and (F.Name <> '..') then
488
          begin
489
            result := result and DeleteDirectory(IncludeTrailingPathDelimiter(Name) + F.Name);
490
          end;
491
        end
492
        else
493
        begin
494
          if not DeleteFile(IncludeTrailingPathDelimiter(Name) + F.Name) then result := false;
495
        end;
496
      until FindNext(F) <> 0;
497
    finally
498
      FindClose(F);
499
    end;
500
    if not RemoveDir(Name) then result := false;
501
  end;
502
end;
503
 
89 daniel-mar 504
function DriveLetterToDriveNumber(driveLetter: AnsiChar): integer;
75 daniel-mar 505
var
506
  tmp: string;
507
begin
89 daniel-mar 508
  tmp := LowerCase(string(driveLetter));
75 daniel-mar 509
  result := Ord(tmp[1])-Ord('a');
510
end;
511
 
512
function GetStringFromDLL(filename: string; num: integer): string;
513
const
514
  // Source: http://www.eggheadcafe.com/forumarchives/vcmfc/sep2005/post23917443.asp
515
  MAX_BUF = 4097; // OK?
516
var
517
  hLib: THandle;
518
  buf: array[0..MAX_BUF] of char;
519
begin
520
  hLib := LoadLibrary(PChar(filename));
521
  try
522
    LoadString(hLib, num, buf, sizeof(buf));
523
    result := buf;
524
  finally
525
    FreeLibrary(hLib);
526
  end;
527
end;
528
 
529
function ExpandEnvStr(const szInput: string): string;
530
const
531
  MAXSIZE = 32768; // laut PSDK sind 32k das Maximum
532
begin
533
  // Source: http://www.delphi-library.de/topic_Umgebungsvariable+in+einem+String+aufloesen_20516,0.html
534
  SetLength(Result,MAXSIZE);
535
  SetLength(Result,ExpandEnvironmentStrings(pchar(szInput),
536
    @Result[1],length(Result))-1); //-1 um abschließendes #0 zu verwerfen
537
end;
538
 
539
function DecodeReferenceString(s: string): string;
540
var
541
  dll, id, lang, cache: string;
542
  sl, sl2: tstringlist;
543
begin
544
  // Beispiele
545
  // Papierkorb                                                 -- Windows 95
546
  // @C:\WINNT\system32\shell32.dll,-8964@1031,Papierkorb       -- Windows 2000
547
 
548
  if Copy(s, 1, 1) = '@' then
549
  begin
550
    // Referenz auf eine DLL
551
    // @<dll>,-<id>[@<lang>][,<cache>]
552
 
553
    sl := TStringList.Create;
554
    try
555
      // '@' am Anfang entfernen
556
      s := Copy(s, 2, length(s)-1);
557
 
558
      // Nach ',' auftrennen
559
      // sl[0] --> dll
560
      // sl[1] --> -id@lang
561
      // sl[2] --> cache
562
      sl.CommaText := s;
563
 
564
      if sl.Count > 2 then
565
      begin
566
        // Das Ergebnis ist bereits im Klartext vorhanden und muss nicht extrahiert werden
567
        // Ist bei Windows 2000 der Fall
568
        cache := sl[2];
569
        result := cache;
570
        exit;
571
      end;
572
 
573
      if sl.Count > 1 then
574
      begin
575
        dll := sl[0];
576
 
577
        sl2 := TStringList.Create;
578
        try
579
          // Nach '@' auftrennen
580
          // sl2[0] --> id
581
          // sl2[1] --> lang
582
          sl2.CommaText := StringReplace(sl[1], '@', ',', [rfReplaceAll]);
583
 
584
          id := sl2[0];
585
 
586
          if sl2.Count > 1 then
587
          begin
588
            // ToDo: In Zukunft beachten, sofern möglich
589
            lang := sl2[1];
590
          end;
591
 
592
          // Umgebungsvariablen erkennen und Minuszeichen entfernen
593
          result := GetStringFromDLL(ExpandEnvStr(dll), -StrToInt(id));
594
        finally
595
          sl2.Free;
596
        end;
597
      end
598
      else
599
      begin
600
        // Zu wenige Informationen!
601
 
602
        result := '';
603
      end;
604
    finally
605
      sl.Free;
606
    end;
607
  end
608
  else
609
  begin
610
    // Kein Hinweis auf eine Referenz
611
    result := s;
612
  end;
613
end;
614
 
615
function GPBoolToString(value: GPOLICYBOOL): string;
616
begin
617
  case value of
618
    gpUndefined: result := 'Not configured';
619
    gpEnabled: result := 'Enabled';
620
    gpDisabled: result := 'Disabled';
621
  end;
622
end;
623
 
624
{ TRbRecycleBin }
625
 
626
constructor TRbRecycleBin.Create(AFileOrDirectory: string; ASID: string='');
627
begin
628
  inherited Create;
629
 
630
  FFileOrDirectory := AFileOrDirectory;
631
  FSID := ASID;
76 daniel-mar 632
  TolerantReading := true;
75 daniel-mar 633
end;
634
 
635
// TODO: also a function that tests if the data files are still existing
636
function TRbRecycleBin.CheckIndexes(slErrors: TStrings): boolean;
637
 
638
  procedure _Assert(assertion: boolean; msg: string; args: array of const);
639
  begin
640
    if not assertion then
641
    begin
642
      slErrors.Add(Format(msg, args));
643
      result := false;
644
    end;
645
  end;
646
 
647
  procedure _HandleIndexFile(AFile: string);
648
  var
649
    fs: TFileStream;
76 daniel-mar 650
    infoHdr: TRbInfoHeader;
75 daniel-mar 651
  resourcestring
652
    LNG_IDXERR_VISTA_FILESIZE = '%s: Vista index file has wrong size';
653
    LNG_IDXERR_INFO_RECSIZE_UNEXPECTED = '%s: record size unexpected';
654
    LNG_IDXERR_INFO_UNEXPECTED_EOF = '%s: file size wrong';
655
  begin
656
    fs := TFileStream.Create(AFile, fmOpenRead);
657
    try
658
      fs.Seek(0, soFromBeginning);
659
 
660
      if SameText(copy(ExtractFileName(AFile), 1, 2), '$I') then
661
      begin
662
        _Assert(fs.Size = SizeOf(TRbVistaItem), LNG_IDXERR_VISTA_FILESIZE, [AFile]);
663
      end
76 daniel-mar 664
      else if SameText(ExtractFileName(AFile), 'INFO') or
665
              SameText(ExtractFileName(AFile), 'INFO2') then
75 daniel-mar 666
      begin
667
        fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
76 daniel-mar 668
        _Assert((infoHdr.recordLength = SizeOf(TRbInfoRecordA)) or
669
                (infoHdr.recordLength = SizeOf(TRbInfoRecordW)), LNG_IDXERR_INFO_RECSIZE_UNEXPECTED, [AFile]);
75 daniel-mar 670
        _Assert((fs.Size-fs.Position) mod infoHdr.recordLength = 0, LNG_IDXERR_INFO_UNEXPECTED_EOF, [AFile]);
76 daniel-mar 671
        // TODO: we can also check infoHdr.totalSize or infoHdr.totalEntries
75 daniel-mar 672
      end
76 daniel-mar 673
      else Assert(false);
75 daniel-mar 674
 
675
      // TODO: we could check each item for invalid stuff...?
676
    finally
677
      FreeAndNil(fs);
678
    end;
679
  end;
680
 
681
  procedure _HandleVistaDir(ADirectory: string);
682
  var
683
    SR: TSearchRec;
684
  begin
685
    ADirectory := IncludeTrailingPathDelimiter(ADirectory);
686
 
687
    if FindFirst(ADirectory + '$I*', faAnyFile, SR) = 0 then
688
    begin
689
      repeat
690
        _HandleIndexFile(ADirectory+sr.Name);
691
      until FindNext(SR) <> 0;
692
    end;
693
    FindClose(SR);
694
  end;
695
 
696
begin
697
  result := true;
698
 
699
  if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
700
  begin
701
    _HandleVistaDir(FFileOrDirectory);
702
 
703
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
704
    begin
705
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
706
    end;
707
 
708
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
709
    begin
710
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
711
    end;
712
  end
713
  else if FileExists(FFileOrDirectory) then
714
  begin
715
    _HandleIndexFile(FFileOrDirectory);
716
  end
717
  else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
718
end;
719
 
720
function TRbRecycleBin.GetItem(id: string): TRbRecycleBinItem;
721
 
722
  procedure _HandleIndexFile(AFile: string);
723
  var
724
    fs: TFileStream;
76 daniel-mar 725
    infoHdr: TRbInfoHeader;
75 daniel-mar 726
    testItem: TRbRecycleBinItem;
727
  begin
728
    fs := TFileStream.Create(AFile, fmOpenRead);
729
    try
730
      fs.Seek(0, soFromBeginning);
731
 
82 daniel-mar 732
      if SameText(ExtractFileName(AFile), '$I'+id) then
75 daniel-mar 733
      begin
82 daniel-mar 734
        result := TRbVistaItem.Create(fs, AFile, id);
75 daniel-mar 735
      end
736
      else
737
      begin
738
        fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
739
        case infoHdr.recordLength of
76 daniel-mar 740
          SizeOf(TRbInfoRecordA):
75 daniel-mar 741
          begin
742
            while fs.Position < fs.size do
743
            begin
76 daniel-mar 744
              testItem := TRbInfoAItem.Create(fs, AFile);
75 daniel-mar 745
              if testItem.ID = id then
746
              begin
747
                result := testItem;
748
                break;
749
              end;
750
            end;
751
          end;
76 daniel-mar 752
          SizeOf(TRbInfoRecordW):
75 daniel-mar 753
          begin
754
            while fs.Position < fs.size do
755
            begin
76 daniel-mar 756
              testItem := TRbInfoWItem.Create(fs, AFile);
75 daniel-mar 757
              if testItem.ID = id then
758
              begin
759
                result := testItem;
760
                break;
761
              end;
762
            end;
763
          end
764
          else
765
          begin
766
            raise Exception.CreateFmt(LNG_INVALID_INFO_FORMAT, [AFile]);
767
          end;
768
        end;
769
      end;
770
    finally
771
      FreeAndNil(fs);
772
    end;
773
  end;
774
 
775
  procedure _HandleVistaDir(ADirectory: string);
776
  var
777
    SR: TSearchRec;
778
    fs: TFileStream;
779
    id: string;
780
  begin
781
    ADirectory := IncludeTrailingPathDelimiter(ADirectory);
782
 
783
    if FileExists(ADirectory + '$I' + id) then
784
    begin
785
      fs := TFileStream.Create(ADirectory+sr.Name, fmOpenRead);
786
      try
787
        fs.Seek(0, soFromBeginning);
788
        result := TRbVistaItem.Create(fs, ADirectory+sr.Name, id);
789
      finally
790
        FreeAndNil(fs);
791
      end;
792
    end;
793
  end;
794
 
795
begin
796
  result := nil;
797
 
798
  if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
799
  begin
800
    _HandleVistaDir(FFileOrDirectory);
801
    if Assigned(result) then exit;
802
 
803
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
804
    begin
805
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
806
      if Assigned(result) then exit;
807
    end;
808
 
809
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
810
    begin
811
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
812
      if Assigned(result) then exit;
813
    end;
814
  end
815
  else if FileExists(FFileOrDirectory) then
816
  begin
817
    _HandleIndexFile(FFileOrDirectory);
818
    if Assigned(result) then exit;
819
  end
820
  else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
821
end;
822
 
823
procedure TRbRecycleBin.ListItems(list: TObjectList{TRbRecycleBinItem});
824
 
825
  procedure _HandleIndexFile(AFile: string);
826
  var
827
    fs: TFileStream;
76 daniel-mar 828
    infoHdr: TRbInfoHeader;
75 daniel-mar 829
    testID: string;
76 daniel-mar 830
    wTest: TRbInfoWItem;
831
    bakPosition: int64;
75 daniel-mar 832
  begin
833
    fs := TFileStream.Create(AFile, fmOpenRead);
834
    try
835
      fs.Seek(0, soFromBeginning);
836
 
82 daniel-mar 837
      if SameText(copy(ExtractFileName(AFile), 1, 2), '$I') then
75 daniel-mar 838
      begin
82 daniel-mar 839
        testID := copy(testID, 3, Length(testID)-2);
75 daniel-mar 840
        list.Add(TRbVistaItem.Create(fs, AFile, testID));
841
      end
842
      else
843
      begin
76 daniel-mar 844
        if TolerantReading then
845
        begin
846
          // This is a special treatment how to recover data from an INFO/INFO2 file
847
          // which was corrupted by an incompatible multiboot configuration.
848
          // Example:
849
          // - Win95 without IE4 and WinNT4 both write into the INFO file. But Win95 appends the ANSI record and WinNT appends an Unicode record.
850
          // - Win95 with IE4 and Windows 2000/2003/XP write into the INFO2 file. But Win9x appends the ANSI record and Win2k+ appends an Unicode record.
851
          fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
852
          while fs.Position < fs.size do
75 daniel-mar 853
          begin
76 daniel-mar 854
            // Can we actually read a Unicode record?
855
            if fs.Position + SizeOf(TRbInfoRecordW) <= fs.Size then
75 daniel-mar 856
            begin
76 daniel-mar 857
              // Try to read the Unicode record and check if it is valid
858
              // In case it is no Unicode record, then the Unicode part will be the
859
              // ANSI source name of the next record. In this case, we won't get
860
              // a ':' at the Unicode string.
861
              bakPosition := fs.Position;
862
              wTest := TRbInfoWItem.Create(fs, AFile);
863
              if Copy(wTest.SourceUnicode, 2, 1) = ':' then
864
              begin
865
                // Yes, it is a valid Unicode record.
866
                list.Add(wTest);
867
              end
868
              else
869
              begin
870
                // No, it is not a valid Unicode record. Jump back, and we need
871
                // to assume that the following record will be a valid ANSI record.
872
                fs.Position := bakPosition;
873
                list.Add(TRbInfoAItem.Create(fs, AFile));
874
              end;
875
            end
876
            else
877
            begin
878
              // No, there is not enough space left for an Unicode record.
879
              // So we assume that the following record will be a valid ANSI record.
880
              list.Add(TRbInfoAItem.Create(fs, AFile));
75 daniel-mar 881
            end;
882
          end;
76 daniel-mar 883
        end
884
        else
885
        begin
886
          // This is the code for non-tolerant reading of the records.
887
          fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
888
          case infoHdr.recordLength of
889
            SizeOf(TRbInfoRecordA):
75 daniel-mar 890
            begin
76 daniel-mar 891
              while fs.Position < fs.size do
892
              begin
893
                list.Add(TRbInfoAItem.Create(fs, AFile));
894
              end;
75 daniel-mar 895
            end;
76 daniel-mar 896
            SizeOf(TRbInfoRecordW):
897
            begin
898
              while fs.Position < fs.size do
899
              begin
900
                list.Add(TRbInfoWItem.Create(fs, AFile));
901
              end;
902
            end
903
            else
904
            begin
905
              raise Exception.CreateFmt(LNG_INVALID_INFO_FORMAT, [AFile]);
906
            end;
75 daniel-mar 907
          end;
908
        end;
909
      end;
910
    finally
911
      FreeAndNil(fs);
912
    end;
913
  end;
914
 
915
  procedure _HandleVistaDir(ADirectory: string);
916
  var
917
    SR: TSearchRec;
918
    fs: TFileStream;
919
    id: string;
920
  begin
921
    ADirectory := IncludeTrailingPathDelimiter(ADirectory);
922
 
923
    if FindFirst(ADirectory + '$I*', faAnyFile, SR) = 0 then
924
    begin
925
      repeat
926
        id := sr.Name;
927
        { id := ChangeFileExt(id, ''); }  // Removed code: We keep the file extention as part of the ID, because we do not know if the ID is otherwise unique
928
        id := Copy(id, 3, Length(id)-2);
929
 
930
        fs := TFileStream.Create(ADirectory+sr.Name, fmOpenRead);
931
        try
932
          fs.Seek(0, soFromBeginning);
933
          list.Add(TRbVistaItem.Create(fs, ADirectory+sr.Name, id));
934
        finally
935
          FreeAndNil(fs);
936
        end;
937
      until FindNext(SR) <> 0;
938
    end;
939
    FindClose(SR);
940
  end;
941
 
942
begin
943
  if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
944
  begin
945
    _HandleVistaDir(FFileOrDirectory);
946
 
947
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
948
    begin
949
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
950
    end;
951
 
952
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
953
    begin
954
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
955
    end;
956
  end
957
  else if FileExists(FFileOrDirectory) then
958
  begin
76 daniel-mar 959
    _HandleIndexFile(FFileOrDirectory); // Either INFO, or INFO2, or a single Vista index file
75 daniel-mar 960
  end
961
  else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
962
end;
963
 
964
{ TRbDrive }
965
 
966
procedure TRbDrive.CheckDriveExisting;
967
begin
968
  // Does the drive exist?
969
  // see http://www.delphipraxis.net/post2933.html
78 daniel-mar 970
  if not GetLogicalDrives and (1 shl DriveNumber) <> 0 then
75 daniel-mar 971
  begin
89 daniel-mar 972
    raise EInvalidDrive.CreateFmt(LNG_DRIVE_NOT_EXISTING, [UpperCase(string(FDriveLetter))+':']);
75 daniel-mar 973
  end;
974
end;
975
 
89 daniel-mar 976
constructor TRbDrive.Create(ADriveLetter: AnsiChar);
75 daniel-mar 977
begin
978
  inherited Create;
979
 
980
  FDriveLetter := ADriveLetter;
981
  CheckDriveExisting;
982
end;
983
 
78 daniel-mar 984
function TRbDrive.DiskSize: integer;
985
begin
986
  result := SysUtils.DiskSize(DriveNumber+1 {0 is current, 1 is A}) div (1024*1024);
987
end;
988
 
989
function TRbDrive.DriveNumber: integer;
990
begin
991
  result := DriveLetterToDriveNumber(FDriveLetter);
992
end;
993
 
75 daniel-mar 994
function TRbDrive.GetAPIInfo: TSHQueryRBInfo;
995
var
996
  PSHQueryRecycleBin: TSHQueryRecycleBin;
997
  RBHandle: THandle;
998
  res: HRESULT;
999
  Path: string;
1000
begin
1001
  Path := FDriveLetter + ':\';
1002
 
1003
  // Ref: http://www.delphipraxis.net/post1291.html
1004
 
1005
  RBHandle := LoadLibrary(shell32);
1006
  try
1007
    PSHQueryRecycleBin := nil;
1008
    if RBHandle <> 0 then
1009
    begin
1010
      PSHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
1011
      if not Assigned(@PSHQueryRecycleBin) then
1012
      begin
1013
        FreeLibrary(RBHandle);
1014
        RBHandle := 0;
1015
      end;
1016
    end;
1017
 
1018
    FillChar(result, SizeOf(TSHQueryRBInfo), 0);
1019
    result.cbSize := SizeOf(TSHQueryRBInfo);
1020
 
1021
    if (RBHandle <> 0) and Assigned(PSHQueryRecycleBin) then
1022
    begin
1023
      res := PSHQueryRecycleBin(PChar(Path), result);
1024
      // if Succeeded(res) then
1025
      if res = S_OK then
1026
      begin
1027
        // Alles OK, unser result hat nun die gewünschten Daten.
1028
      end
1029
      else
1030
      begin
1031
        // Since Windows Vista, SHQueryRecycleBin will fail with E_FAIL (80004005)
1032
        // if Path is a floppy or CD drive...
1033
        raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_ERROR_CODE, [C_SHQueryRecycleBin, Path, '0x'+IntToHex(res, 2*SizeOf(HRESULT))])]);
1034
      end;
1035
    end
1036
    else
1037
      raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHQueryRecycleBin])]);
1038
  finally
1039
    if RBHandle <> 0 then FreeLibrary(RBHandle);
1040
  end;
1041
end;
1042
 
78 daniel-mar 1043
function TRbDrive.GetMaxPercentUsage: Extended;
75 daniel-mar 1044
var
78 daniel-mar 1045
  abs: integer; // in MB
1046
  rel: integer; // in % (0-100)
1047
  gpSetting: integer;
1048
const
1049
  DEFAULT_PERCENT = 10; // Windows 95 default
1050
begin
1051
  gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
1052
  if gpSetting <> -1 then
1053
    result := gpSetting / 100
1054
  else if TRecycleBinManager.UsesGlobalSettings then
1055
    result := TRecycleBinManager.GetGlobalMaxPercentUsage / 100
1056
  else if OldCapacityPercent(rel) then
1057
  begin
1058
    result := rel / 100;
1059
  end
1060
  else if NewCapacityAbsolute(abs) then
1061
  begin
1062
    result := abs / DiskSize;
1063
  end
1064
  else
1065
  begin
1066
    result := DEFAULT_PERCENT / 100;
1067
  end;
1068
end;
1069
 
1070
function TRbDrive.GetMaxAbsoluteUsage: integer;
1071
var
1072
  abs: integer; // in MB
1073
  rel: integer; // in % (0-100)
1074
  gpSetting: integer;
1075
const
1076
  DEFAULT_PERCENT = 10; // Windows 95 default
1077
begin
1078
  gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
1079
  if gpSetting <> -1 then
1080
    result := Ceil(gpSetting/100 * DiskSize)
1081
  else if TRecycleBinManager.UsesGlobalSettings then
1082
    result := Ceil(TRecycleBinManager.GetGlobalMaxPercentUsage/100 * DiskSize)
1083
  else if NewCapacityAbsolute(abs) then
1084
  begin
1085
    result := abs;
1086
  end
1087
  else if OldCapacityPercent(rel) then
1088
  begin
1089
    result := Ceil(rel/100 * DiskSize);
1090
  end
1091
  else
1092
  begin
1093
    result := Ceil(DEFAULT_PERCENT/100 * DiskSize);
1094
  end;
1095
end;
1096
 
1097
function TRbDrive.OldCapacityPercent(var res: integer): boolean;
1098
var
75 daniel-mar 1099
  reg: TRegistry;
1100
  purgeInfo: TRbWin95PurgeInfo;
1101
begin
78 daniel-mar 1102
  if Win32MajorVersion >= 6 then
1103
  begin
1104
    // Only available till Windows XP
1105
    result := false;
1106
    exit;
1107
  end;
75 daniel-mar 1108
 
78 daniel-mar 1109
  result := false;
1110
 
75 daniel-mar 1111
  reg := TRegistry.Create;
1112
  try
1113
    reg.RootKey := HKEY_LOCAL_MACHINE;
1114
 
1115
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1116
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1117
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1118
    begin
89 daniel-mar 1119
      if reg.OpenKeyReadOnly(string(FDriveLetter)) then
75 daniel-mar 1120
      begin
1121
        if reg.ValueExists('Percent') then
1122
        begin
1123
          // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1124
 
78 daniel-mar 1125
          res := reg.ReadInteger('Percent');
1126
          result := true;
75 daniel-mar 1127
        end;
1128
      end
1129
      else
1130
      begin
1131
        if reg.ValueExists('PurgeInfo') then
1132
        begin
1133
          // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1134
 
1135
          reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1136
 
78 daniel-mar 1137
          res := purgeInfo.percentDrive[FDriveLetter];
1138
          result := true;
75 daniel-mar 1139
        end;
1140
      end;
1141
 
1142
      reg.CloseKey;
1143
    end;
1144
  finally
1145
    reg.Free;
1146
  end;
1147
end;
1148
 
78 daniel-mar 1149
function TRbDrive.NewCapacityAbsolute(var res: integer): boolean;
75 daniel-mar 1150
var
78 daniel-mar 1151
  reg: TRegistry;
75 daniel-mar 1152
begin
78 daniel-mar 1153
  if Win32MajorVersion < 6 then
1154
  begin
1155
    // Only available since Windows Vista
1156
    result := false;
1157
    exit;
1158
  end;
75 daniel-mar 1159
 
78 daniel-mar 1160
  result := false;
1161
 
1162
  reg := TRegistry.Create;
1163
  try
1164
    reg.RootKey := HKEY_CURRENT_USER;
1165
 
1166
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
1167
    begin
1168
      // Windows Vista and upwards
1169
      if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
1170
      begin
1171
        res := reg.ReadInteger('MaxCapacity'); // in MB
1172
        result := true;
1173
      end;
1174
      reg.CloseKey;
1175
    end;
1176
  finally
1177
    reg.Free;
1178
  end;
75 daniel-mar 1179
end;
1180
 
1181
function TRbDrive.GetNukeOnDelete: boolean;
1182
var
1183
  reg: TRegistry;
1184
  purgeInfo: TRbWin95PurgeInfo;
1185
const
78 daniel-mar 1186
  RES_DEFAULT = false; // Windows 95 default
75 daniel-mar 1187
begin
78 daniel-mar 1188
  if TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles = gpEnabled then
1189
    result := true
1190
  else if TRecycleBinManager.UsesGlobalSettings then
1191
    result := TRecycleBinManager.GetGlobalNukeOnDelete
1192
  else
1193
  begin
1194
    result := RES_DEFAULT;
75 daniel-mar 1195
 
78 daniel-mar 1196
    reg := TRegistry.Create;
1197
    try
1198
      reg.RootKey := HKEY_CURRENT_USER;
75 daniel-mar 1199
 
78 daniel-mar 1200
      if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
75 daniel-mar 1201
      begin
78 daniel-mar 1202
        // Windows Vista and upwards
1203
        if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
75 daniel-mar 1204
        begin
1205
          result := reg.ReadBool('NukeOnDelete');
1206
        end;
78 daniel-mar 1207
        reg.CloseKey;
75 daniel-mar 1208
      end
1209
      else
1210
      begin
78 daniel-mar 1211
        reg.RootKey := HKEY_LOCAL_MACHINE;
1212
 
1213
        // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1214
        // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1215
        if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
75 daniel-mar 1216
        begin
89 daniel-mar 1217
          if reg.OpenKeyReadOnly(string(FDriveLetter)) then
78 daniel-mar 1218
          begin
1219
            if reg.ValueExists('NukeOnDelete') then
1220
            begin
1221
              // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
75 daniel-mar 1222
 
78 daniel-mar 1223
              result := reg.ReadBool('NukeOnDelete');
1224
            end;
1225
          end
1226
          else
1227
          begin
1228
            if reg.ValueExists('PurgeInfo') then
1229
            begin
1230
              // Windows 95 - Kodierte Informationen liegen in PurgeInfo
75 daniel-mar 1231
 
78 daniel-mar 1232
              reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1233
 
1234
              result := ((purgeInfo.NukeOnDeleteBits shr DriveNumber) and 1) = 1;
1235
            end;
1236
          end;
1237
 
1238
          reg.CloseKey;
75 daniel-mar 1239
        end;
1240
      end;
78 daniel-mar 1241
    finally
1242
      reg.Free;
75 daniel-mar 1243
    end;
1244
  end;
1245
end;
1246
 
1247
function TRbDrive.GetNumItems: int64;
1248
begin
1249
  result := GetAPIInfo.i64NumItems;
1250
end;
1251
 
1252
function TRbDrive.GetSize: int64;
1253
begin
1254
  result := GetAPIInfo.i64Size;
1255
end;
1256
 
1257
function TRbDrive.GetVolumeGUID: TGUID;
1258
begin
1259
  if GetDriveGUID(FDriveLetter, result) <> ERROR_SUCCESS then
1260
  begin
1261
    result := NULL_GUID;
1262
  end;
1263
end;
1264
 
77 daniel-mar 1265
function TRbDrive.GetVolumeGUIDAvailable: boolean;
1266
begin
1267
  result := not IsEqualGUID(VolumeGUID, NULL_GUID);
1268
end;
1269
 
75 daniel-mar 1270
function TRbDrive.IsEmpty: boolean;
1271
begin
1272
  result := GetNumItems = 0;
1273
end;
1274
 
1275
function TRbDrive.IsFAT: boolean;
1276
var
1277
  Dummy2: DWORD;
1278
  Dummy3: DWORD;
1279
  FileSystem: array[0..MAX_PATH] of char;
1280
  VolumeName: array[0..MAX_PATH] of char;
1281
  s: string;
1282
begin
1283
  s := FDriveLetter + DriveDelim + PathDelim; // ohne die Auslagerung in einen String kommt es zu einer AV in ntdll
1284
  GetVolumeInformation(PChar(s), VolumeName,
1285
    SizeOf(VolumeName), nil, Dummy2, Dummy3, FileSystem, SizeOf(FileSystem));
1286
  result := uppercase(copy(FileSystem, 0, 3)) = 'FAT';
1287
end;
1288
 
1289
procedure TRbDrive.ListRecycleBins(list: TObjectList{TRbRecycleBin}; UserSID: string='');
1290
 
1291
  procedure _AddSIDFolders(dir: string; wholeFolder: boolean);
1292
  var
1293
    SR: TSearchRec;
1294
  begin
1295
    dir := IncludeTrailingPathDelimiter(dir);
1296
    if FindFirst(dir+'S-*', faAnyFile, SR) = 0 then
1297
    begin
1298
      try
1299
        repeat
1300
          if (SR.Name = '.') or (SR.Name = '..') or not DirectoryExists(dir + SR.Name) then continue;
1301
 
1302
          if wholeFolder then
1303
          begin
1304
            // Vista
1305
            list.Add(TRbRecycleBin.Create(dir+SR.Name, SR.Name));
1306
          end
1307
          else
1308
          begin
1309
            // Win95 .. WinXP
1310
            if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2') then
1311
              list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2', SR.Name));
1312
            if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO') then
1313
              list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO', SR.Name));
1314
          end;
1315
        until FindNext(SR) <> 0;
1316
      finally
1317
        FindClose(SR);
1318
      end;
1319
    end;
1320
  end;
1321
 
1322
var
1323
  dir: string;
1324
begin
1325
  // Find recyclers from Windows Vista or higher
1326
 
1327
  if IsFAT then
1328
  begin
1329
    dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim;
1330
    if DirectoryExists(dir) then
1331
    begin
1332
      list.Add(TRbRecycleBin.Create(dir));
1333
    end;
1334
  end
1335
  else
1336
  begin
1337
    if UserSID <> '' then
1338
    begin
1339
      dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim + UserSID + PathDelim;
1340
      if DirectoryExists(dir) then
1341
      begin
1342
        list.Add(TRbRecycleBin.Create(dir, UserSID));
1343
      end;
1344
    end
1345
    else
1346
    begin
1347
      _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + '$recycle.bin', true);
1348
    end;
1349
  end;
1350
 
1351
  // Find recyclers from Windows before Vista
1352
 
1353
  if IsFAT then
1354
  begin
1355
    dir := FDriveLetter + DriveDelim + PathDelim + 'Recycled' + PathDelim;
1356
 
1357
    // Both "recycle bins" are possible if you have multiboot (but do overwrite themselfes if you empty them)
1358
    if FileExists(dir + 'INFO2') then
76 daniel-mar 1359
      list.Add(TRbRecycleBin.Create(dir + 'INFO2')); // Windows 95 with Internet Explorer 4 Extension or higher Windows 9x versions
75 daniel-mar 1360
    if FileExists(dir + 'INFO') then
1361
      list.Add(TRbRecycleBin.Create(dir + 'INFO')); // Windows 95 native
1362
  end
1363
  else
1364
  begin
1365
    if UserSID <> '' then
1366
    begin
1367
      dir := FDriveLetter + DriveDelim + PathDelim + 'Recycler' + PathDelim + UserSID + PathDelim;
1368
 
1369
      if FileExists(dir + 'INFO2') then
76 daniel-mar 1370
        list.Add(TRbRecycleBin.Create(dir + 'INFO2', UserSID)); // Windows 2000+
75 daniel-mar 1371
      if FileExists(dir + 'INFO') then
1372
        list.Add(TRbRecycleBin.Create(dir + 'INFO', UserSID)); // Windows NT 4
1373
    end
1374
    else
1375
    begin
1376
      _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + 'Recycler', false);
1377
    end;
1378
  end;
1379
end;
1380
 
76 daniel-mar 1381
{ TRbInfoAItem }
75 daniel-mar 1382
 
76 daniel-mar 1383
procedure TRbInfoAItem.ReadFromStream(stream: TStream);
75 daniel-mar 1384
var
76 daniel-mar 1385
  r: TRbInfoRecordA;
75 daniel-mar 1386
begin
1387
  stream.ReadBuffer(r, SizeOf(r));
1388
 
1389
  FSourceDrive := Chr(Ord('A') + r.sourceDrive);
1390
 
76 daniel-mar 1391
  // Win95 with IE4 and Win2000+:
1392
  // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
1393
  // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
1394
  // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
1395
  // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
1396
  // Zwecken eingesetzt werden soll.
75 daniel-mar 1397
  if r.sourceAnsi[0] = #0 then
1398
  begin
1399
    FRemovedEntry := true;
89 daniel-mar 1400
    r.sourceAnsi[0] := AnsiChar(FSourceDrive);
75 daniel-mar 1401
  end;
1402
 
1403
  FSourceAnsi := r.sourceAnsi;
90 daniel-mar 1404
  FSourceUnicode := AnsiCharArrayToWideString(r.sourceAnsi); // Unicode does not exist in INFO(1) structure
75 daniel-mar 1405
  FID := IntToStr(r.recordNumber);
1406
  FDeletionTime := FileTimeToDateTime(r.deletionTime);
1407
  FOriginalSize := r.originalSize;
88 daniel-mar 1408
 
1409
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1410
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1411
  AnsiRemoveNulChars(FSourceAnsi);
1412
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1413
end;
1414
 
76 daniel-mar 1415
function TRbInfoAItem.DeleteFile: boolean;
75 daniel-mar 1416
var
1417
  r: string;
1418
begin
1419
  r := GetPhysicalFile;
1420
  if DirectoryExists(r) then
1421
    result := DeleteDirectory(r) // Usually, the old recycle bin does not allow folders. Just to be sure, we include the code.
1422
  else
1423
    result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung? --> Win95: Funktioniert
1424
 
1425
  // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Datei neu schreiben)
1426
end;
1427
 
76 daniel-mar 1428
function TRbInfoAItem.GetPhysicalFile: string;
75 daniel-mar 1429
begin
1430
  if FRemovedEntry then
1431
  begin
1432
    result := '';
1433
    Exit;
1434
  end;
1435
 
1436
  // e.g. C:\...\DC0.doc
1437
  result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
89 daniel-mar 1438
            'D' + (* SourceDrive *) Source[1] + ID + ExtractFileExt(Source);
75 daniel-mar 1439
end;
1440
 
76 daniel-mar 1441
constructor TRbInfoAItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 1442
begin
1443
  inherited Create;
1444
  ReadFromStream(fs);
1445
  FIndexFile := AIndexFile;
1446
end;
1447
 
76 daniel-mar 1448
{ TRbInfoWItem }
75 daniel-mar 1449
 
76 daniel-mar 1450
procedure TRbInfoWItem.ReadFromStream(stream: TStream);
75 daniel-mar 1451
var
76 daniel-mar 1452
  r: TRbInfoRecordW;
75 daniel-mar 1453
begin
1454
  stream.ReadBuffer(r, SizeOf(r));
1455
 
76 daniel-mar 1456
  // Win95 with IE4 and Win2000+:
1457
  // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
1458
  // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
1459
  // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
75 daniel-mar 1460
  // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
1461
  // Zwecken eingesetzt werden soll.
1462
  if r.sourceAnsi[0] = #0 then
1463
  begin
1464
    FRemovedEntry := true;
1465
    r.sourceAnsi[0] := AnsiChar(r.sourceUnicode[0]);
1466
  end;
1467
 
1468
  FSourceAnsi := r.sourceAnsi;
1469
  FSourceUnicode := r.sourceUnicode;
1470
  FID := IntToStr(r.recordNumber);
1471
  FSourceDrive := Chr(Ord('A') + r.sourceDrive);
1472
  FDeletionTime := FileTimeToDateTime(r.deletionTime);
1473
  FOriginalSize := r.originalSize;
88 daniel-mar 1474
 
1475
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1476
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1477
  AnsiRemoveNulChars(FSourceAnsi);
1478
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1479
end;
1480
 
76 daniel-mar 1481
function TRbInfoWItem.DeleteFile: boolean;
75 daniel-mar 1482
var
1483
  r: string;
1484
begin
1485
  r := GetPhysicalFile;
1486
  if DirectoryExists(r) then
1487
    result := DeleteDirectory(r)
1488
  else
1489
    result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung?
1490
 
1491
  // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Erstes Byte auf 0 setzen)
1492
end;
1493
 
76 daniel-mar 1494
function TRbInfoWItem.GetPhysicalFile: string;
75 daniel-mar 1495
begin
1496
  if FRemovedEntry then
1497
  begin
1498
    result := '';
1499
    Exit;
1500
  end;
1501
 
76 daniel-mar 1502
  (*
1503
  This is actually a bit tricky...
1504
  Win95 will choose the first letter of the AnsiSource name.
1505
  WinNT will choose the first letter of the UnicodeSource name.
1506
  WinXP will choose the driveNumber member.
1507
 
1508
  Windows XP is kinda buggy when it comes to changing a drive letter.
1509
  For example, the drive E: was changed to K:
1510
  The drive letter is 04 (E), the Source name begins with E:\ and the physical file is De0.txt .
1511
  After the recycle bin is opened the first time:
1512
  - The recycle bin will show the file origin as K:\ and not as E:\
1513
  - The file was renamed from De0.txt to Dk0.txt
1514
  - The file can be recovered at this time
1515
  When the recycle bin is closed, the INFO2 file will not be corrected (which is a bug).
1516
  So, if you open the recycle bin again, the record will be marked
1517
  as deleted in the INFO file (the first byte will be set to 0),
1518
  because Windows searches for De0.txt and doesn't find it.
1519
 
1520
  (This comment also applies to TRbInfoAItem.GetPhysicalFile)
1521
  *)
1522
 
75 daniel-mar 1523
  // e.g. C:\...\DC0.doc
1524
  result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
76 daniel-mar 1525
            'D' + SourceDrive (* SourceUnicode[1] *) + ID + ExtractFileExt(SourceUnicode);
75 daniel-mar 1526
end;
1527
 
76 daniel-mar 1528
constructor TRbInfoWItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 1529
begin
1530
  inherited Create;
1531
  ReadFromStream(fs);
1532
  FIndexFile := AIndexFile;
1533
end;
1534
 
1535
{ TRbVistaItem }
1536
 
1537
procedure TRbVistaItem.ReadFromStream(stream: TStream);
1538
var
82 daniel-mar 1539
  r1: TRbVistaRecord1;
1540
  r2: TRbVistaRecord2Head;
1541
  r2SourceUnicode: array of WideChar;
1542
  version: DWORD;
75 daniel-mar 1543
begin
82 daniel-mar 1544
  stream.ReadBuffer(version, SizeOf(version));
75 daniel-mar 1545
 
82 daniel-mar 1546
  if version = 1 then
1547
  begin
1548
    stream.Seek(0, soBeginning);
1549
    stream.ReadBuffer(r1, SizeOf(r1));
90 daniel-mar 1550
    FSourceAnsi := AnsiString(WideCharArrayToWideString(r1.sourceUnicode)); // Invalid chars are automatically converted into '?'
1551
    FSourceUnicode := WideCharArrayToWideString(r1.sourceUnicode);
82 daniel-mar 1552
    FID := ''; // will be added manually (at the constructor)
89 daniel-mar 1553
    FSourceDrive := r1.sourceUnicode[1];
82 daniel-mar 1554
    FDeletionTime := FileTimeToDateTime(r1.deletionTime);
1555
    FOriginalSize := r1.originalSize;
1556
  end
1557
  else if version = 2 then
1558
  begin
1559
    stream.Seek(0, soBeginning);
1560
    stream.ReadBuffer(r2, SizeOf(r2));
1561
 
90 daniel-mar 1562
    SetLength(r2SourceUnicode, SizeOf(WideChar)*(r2.SourceCountChars-1));
1563
    stream.Read(r2SourceUnicode[0], SizeOf(WideChar)*(r2.sourceCountChars-1));
82 daniel-mar 1564
 
90 daniel-mar 1565
    FSourceAnsi := AnsiString(WideCharArrayToWideString(r2sourceUnicode)); // Invalid chars are automatically converted into '?'
1566
    FSourceUnicode := WideCharArrayToWideString(r2sourceUnicode);
82 daniel-mar 1567
    FID := ''; // will be added manually (at the constructor)
89 daniel-mar 1568
    FSourceDrive := r2sourceUnicode[1];
82 daniel-mar 1569
    FDeletionTime := FileTimeToDateTime(r2.deletionTime);
1570
    FOriginalSize := r2.originalSize;
1571
  end
1572
  else
1573
  begin
1574
    raise Exception.CreateFmt('Invalid Vista index format version %d', [version]);
1575
  end;
88 daniel-mar 1576
 
1577
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1578
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1579
  AnsiRemoveNulChars(FSourceAnsi);
1580
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1581
end;
1582
 
1583
function TRbVistaItem.DeleteFile: boolean;
1584
var
1585
  r: string;
1586
begin
1587
  r := GetPhysicalFile;
1588
  if DirectoryExists(r) then
1589
    result := DeleteDirectory(r)
1590
  else
1591
    result := SysUtils.DeleteFile(r);
1592
 
1593
  SysUtils.DeleteFile(FIndexFile);
1594
end;
1595
 
1596
function TRbVistaItem.GetPhysicalFile: string;
1597
begin
1598
  result := FIndexFile;
1599
  result := StringReplace(Result, '$I', '$R', [rfIgnoreCase]);
1600
end;
1601
 
1602
constructor TRbVistaItem.Create(fs: TStream; AIndexFile, AID: string);
1603
begin
1604
  inherited Create;
1605
  ReadFromStream(fs);
1606
  FIndexFile := AIndexFile;
1607
  FID := AID;
1608
end;
1609
 
1610
{ TRecycleBinManager }
1611
 
1612
class function TRecycleBinManager.EmptyOwnRecyclers(flags: cardinal): boolean;
1613
var
1614
  PSHEmptyRecycleBin: TSHEmptyRecycleBin;
1615
  LibHandle: THandle;
1616
begin
1617
  // Source: http://www.dsdt.info/tipps/?id=176
1618
  result := true;
1619
  LibHandle := LoadLibrary(shell32);
1620
  try
1621
    if LibHandle <> 0 then
1622
    begin
1623
      @PSHEmptyRecycleBin := GetProcAddress(LibHandle, C_SHEmptyRecycleBinA);
1624
      if @PSHEmptyRecycleBin <> nil then
1625
      begin
1626
        PSHEmptyRecycleBin(hInstance, nil, flags);
1627
      end
1628
      else
1629
        result := false;
1630
    end
1631
    else
1632
      result := false;
1633
  finally
1634
    @PSHEmptyRecycleBin := nil;
1635
    if LibHandle <> 0 then FreeLibrary(LibHandle);
1636
  end;
1637
end;
1638
 
1639
class function TRecycleBinManager.EmptyOwnRecyclers(sound, progress, confirmation: boolean): boolean;
1640
const
1641
  SHERB_NOCONFIRMATION = $00000001;
1642
  SHERB_NOPROGRESSUI   = $00000002;
1643
  SHERB_NOSOUND        = $00000004;
1644
var
1645
  flags: cardinal;
1646
begin
1647
  flags := 0;
1648
 
1649
  if not progress then
1650
    flags := flags or SHERB_NOPROGRESSUI;
1651
  if not confirmation then
1652
    flags := flags or SHERB_NOCONFIRMATION;
1653
  if not sound then
1654
    flags := flags or SHERB_NOSOUND;
1655
 
1656
  result := EmptyOwnRecyclers(flags);
1657
end;
1658
 
1659
class function TRecycleBinManager.GetGlobalMaxPercentUsage: integer;
1660
var
1661
  reg: TRegistry;
1662
  purgeInfo: TRbWin95PurgeInfo;
1663
const
1664
  RES_DEFAULT = 10; // Windows 95 - Standardwert
1665
begin
78 daniel-mar 1666
  if Win32MajorVersion >= 6 then
1667
  begin
1668
    // Only available till Windows XP
1669
    result := -1;
1670
    exit;
1671
  end;
1672
 
75 daniel-mar 1673
  result := RES_DEFAULT;
1674
 
1675
  reg := TRegistry.Create;
1676
  try
1677
    reg.RootKey := HKEY_LOCAL_MACHINE;
1678
 
1679
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1680
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1681
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1682
    begin
1683
      if reg.ValueExists('Percent') then
1684
      begin
1685
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1686
 
1687
        result := reg.ReadInteger('Percent');
1688
      end
1689
      else if reg.ValueExists('PurgeInfo') then
1690
      begin
1691
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1692
 
1693
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1694
        result := purgeInfo.percentGlobal;
1695
      end;
1696
 
1697
      reg.CloseKey;
1698
    end;
1699
  finally
1700
    reg.Free;
1701
  end;
1702
end;
1703
 
1704
class function TRecycleBinManager.GetGlobalNukeOnDelete: boolean;
1705
var
1706
  reg: TRegistry;
1707
  purgeInfo: TRbWin95PurgeInfo;
1708
const
1709
  RES_DEFAULT = false; // Windows 95 - Standardwert
1710
begin
78 daniel-mar 1711
  if Win32MajorVersion >= 6 then
1712
  begin
1713
    // Only available till Windows XP
1714
    result := false;
1715
    exit;
1716
  end;
1717
 
75 daniel-mar 1718
  result := RES_DEFAULT;
1719
 
1720
  reg := TRegistry.Create;
1721
  try
1722
    reg.RootKey := HKEY_LOCAL_MACHINE;
1723
 
1724
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1725
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1726
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1727
    begin
1728
      if reg.ValueExists('NukeOnDelete') then
1729
      begin
1730
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1731
 
1732
        result := reg.ReadBool('NukeOnDelete');
1733
      end
1734
      else if reg.ValueExists('PurgeInfo') then
1735
      begin
1736
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1737
 
1738
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1739
        result := (purgeInfo.NukeOnDeleteBits and $8000000) = $8000000; // bit 27
1740
      end;
1741
 
1742
      reg.CloseKey;
1743
    end;
1744
  finally
1745
    reg.Free;
1746
  end;
1747
end;
1748
 
76 daniel-mar 1749
(* TODO:
1750
There are more registry values (found in WinXP):
1751
 
1752
BitBucket\<driveletter>
1753
  VolumeSerialNumber
1754
  IsUnicode
1755
 
1756
*)
1757
 
75 daniel-mar 1758
class function TRecycleBinManager.UsesGlobalSettings: boolean;
1759
var
1760
  reg: TRegistry;
1761
  purgeInfo: TRbWin95PurgeInfo;
1762
const
1763
  RES_DEFAULT = true; // Windows 95 - Standardwert
1764
begin
78 daniel-mar 1765
  if Win32MajorVersion >= 6 then
1766
  begin
1767
    // Only available till Windows XP
1768
    result := false;
1769
    exit;
1770
  end;
1771
 
75 daniel-mar 1772
  result := RES_DEFAULT;
1773
 
1774
  reg := TRegistry.Create;
1775
  try
1776
    reg.RootKey := HKEY_LOCAL_MACHINE;
1777
 
1778
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1779
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1780
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1781
    begin
1782
      if reg.ValueExists('UseGlobalSettings') then
1783
      begin
1784
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1785
 
1786
        result := reg.ReadBool('UseGlobalSettings');
1787
      end
1788
      else if reg.ValueExists('PurgeInfo') then
1789
      begin
1790
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1791
 
1792
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1793
        result := purgeInfo.bGlobalSettings;
1794
      end;
1795
 
1796
      reg.CloseKey;
1797
    end;
1798
  finally
1799
    reg.Free;
1800
  end;
1801
end;
1802
 
1803
class procedure TRecycleBinManager.ListDrives(list: TObjectList{TRbDrive});
1804
var
89 daniel-mar 1805
  drive: AnsiChar;
75 daniel-mar 1806
begin
1807
  for drive := 'A' to 'Z' do
1808
    if RecycleBinPossible(drive) then
1809
      list.Add(TRbDrive.Create(drive));
1810
end;
1811
 
1812
class function TRecycleBinManager.OwnRecyclersEmpty: boolean;
1813
var
1814
  drives: TObjectList;
1815
  i: integer;
1816
begin
1817
  result := true;
1818
 
1819
  drives := TObjectList.Create(true);
1820
  try
1821
    ListDrives(drives);
1822
    for i := 0 to drives.Count - 1 do
1823
    begin
1824
      result := result and TRbDrive(drives.Items[i]).IsEmpty;
1825
      if not result then break;
1826
    end;
1827
  finally
1828
    drives.Free;
1829
  end;
1830
end;
1831
 
1832
class function TRecycleBinManager.OwnRecyclersNumItems: int64;
1833
var
1834
  drives: TObjectList;
1835
  i: integer;
1836
begin
1837
  result := 0;
1838
 
1839
  drives := TObjectList.Create(true);
1840
  try
1841
    ListDrives(drives);
1842
    for i := 0 to drives.Count - 1 do
1843
    begin
1844
      result := result + TRbDrive(drives.Items[i]).GetNumItems;
1845
    end;
1846
  finally
1847
    drives.Free;
1848
  end;
1849
end;
1850
 
1851
class function TRecycleBinManager.OwnRecyclersSize: int64;
1852
var
1853
  drives: TObjectList;
1854
  i: integer;
1855
begin
1856
  result := 0;
1857
 
1858
  drives := TObjectList.Create(true);
1859
  try
1860
    ListDrives(drives);
1861
    for i := 0 to drives.Count - 1 do
1862
    begin
1863
      result := result + TRbDrive(drives.Items[i]).GetSize;
1864
    end;
1865
  finally
1866
    drives.Free;
1867
  end;
1868
end;
1869
 
89 daniel-mar 1870
class function TRecycleBinManager.RecycleBinPossible(Drive: AnsiChar): boolean;
75 daniel-mar 1871
var
1872
  typ: Integer;
1873
begin
1874
  // Does the drive exist?
1875
  // see http://www.delphipraxis.net/post2933.html
1876
  result := GetLogicalDrives and (1 shl DriveLetterToDriveNumber(Drive)) <> 0;
1877
  if not result then exit;
1878
 
1879
  // Is it a fixed drive? (Only they can have recycle bins)
1880
  // TODO: is that correct, or can also have other drive types have recyclers?
1881
  typ := GetDriveType(PChar(Drive + ':\'));
1882
  result := typ = DRIVE_FIXED;
1883
end;
1884
 
1885
class function TRecycleBinManager.RecyclerGetCurrentIconString: string;
1886
begin
1887
  if OwnRecyclersEmpty then
1888
    result := RecyclerGetEmptyIconString
1889
  else
1890
    result := RecyclerGetFullIconString;
1891
end;
1892
 
1893
class function TRecycleBinManager.RecyclerGetDefaultIconString: string;
1894
var
1895
  reg: TRegistry;
1896
begin
1897
  // Please note: The "default" icon is not always the icon of the
1898
  // current recycle bin in its current state (full, empty)
1899
  // At Windows 95b, the registry value actually did change every time the
1900
  // recycle bin state did change, but at Windows 2000 I could not see any
1901
  // update, even after reboot. So, the registry value is possible fixed as
1902
  // default = empty on newer OS versions.
1903
 
1904
  reg := TRegistry.Create;
1905
  try
1906
    reg.RootKey := HKEY_CLASSES_ROOT;
1907
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
1908
    begin
1909
      result := reg.ReadString('');
1910
      reg.CloseKey;
1911
    end;
1912
  finally
1913
    reg.Free;
1914
  end;
1915
end;
1916
 
1917
class function TRecycleBinManager.RecyclerGetEmptyIconString: string;
1918
var
1919
  reg: TRegistry;
1920
begin
1921
  reg := TRegistry.Create;
1922
  try
1923
    reg.RootKey := HKEY_CLASSES_ROOT;
1924
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
1925
    begin
1926
      result := reg.ReadString('Empty');
1927
      reg.CloseKey;
1928
    end;
1929
  finally
1930
    reg.Free;
1931
  end;
1932
end;
1933
 
1934
class function TRecycleBinManager.RecyclerGetFullIconString: string;
1935
var
1936
  reg: TRegistry;
1937
begin
1938
  reg := TRegistry.Create;
1939
  try
1940
    reg.RootKey := HKEY_CLASSES_ROOT;
1941
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
1942
    begin
1943
      result := reg.ReadString('Full');
1944
      reg.CloseKey;
1945
    end;
1946
  finally
1947
    reg.Free;
1948
  end;
1949
end;
1950
 
1951
class function TRecycleBinManager.RecyclerGetInfoTip: string;
1952
var
1953
  reg: TRegistry;
1954
begin
1955
  // Not available in some older versions of Windows
1956
 
1957
  reg := TRegistry.Create;
1958
  try
1959
    reg.RootKey := HKEY_CLASSES_ROOT;
1960
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
1961
    begin
1962
      result := reg.ReadString('InfoTip');
1963
      result := DecodeReferenceString(result);
1964
 
1965
      reg.CloseKey;
1966
    end;
1967
  finally
1968
    reg.Free;
1969
  end;
1970
end;
1971
 
1972
class function TRecycleBinManager.RecyclerGetIntroText: string;
1973
var
1974
  reg: TRegistry;
1975
begin
1976
  // Not available in some older versions of Windows
1977
 
1978
  reg := TRegistry.Create;
1979
  try
1980
    reg.RootKey := HKEY_CLASSES_ROOT;
1981
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
1982
    begin
1983
      result := reg.ReadString('IntroText');
1984
      result := DecodeReferenceString(result);
1985
 
1986
      reg.CloseKey;
1987
    end;
1988
  finally
1989
    reg.Free;
1990
  end;
1991
end;
1992
 
1993
class function TRecycleBinManager.RecyclerGetName: string;
1994
var
1995
  reg: TRegistry;
1996
begin
1997
  // Windows 95b:
1998
  // Change of CLSID\{645FF040-5081-101B-9F08-00AA002F954E} will change the desktop name of the recycle bin.
1999
 
2000
  // Windows 2000: If LocalizedString is available, the 3rd argument will be parsed
2001
  // (if the third argument will removed, it will be read out from the DLL resource string automatically)
2002
 
2003
  reg := TRegistry.Create;
2004
  try
2005
    reg.RootKey := HKEY_CLASSES_ROOT;
2006
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
2007
    begin
2008
      if reg.ValueExists('LocalizedString') then
2009
      begin
2010
        result := reg.ReadString('LocalizedString');
2011
        result := DecodeReferenceString(result);
2012
      end
2013
      else
2014
      begin
2015
        result := reg.ReadString('');
2016
      end;
2017
 
2018
      reg.CloseKey;
2019
    end;
2020
  finally
2021
    reg.Free;
2022
  end;
2023
end;
2024
 
2025
class function TRecycleBinManager.RecyclerEmptyEventGetName: string;
2026
var
2027
  reg: TRegistry;
2028
begin
2029
  reg := TRegistry.Create;
2030
  try
2031
    reg.RootKey := HKEY_CURRENT_USER;
2032
    if reg.OpenKeyReadOnly('AppEvents\EventLabels\EmptyRecycleBin') then
2033
    begin
2034
      result := reg.ReadString('');
2035
      reg.CloseKey;
2036
    end;
2037
  finally
2038
    reg.Free;
2039
  end;
2040
end;
2041
 
2042
class function TRecycleBinManager.RecyclerEmptyEventGetCurrentSound: string;
2043
begin
2044
  result := RecyclerEmptyEventGetSound('.Current');
2045
end;
2046
 
2047
class function TRecycleBinManager.RecyclerEmptyEventGetDefaultSound: string;
2048
begin
2049
  result := RecyclerEmptyEventGetSound('.Default');
2050
end;
2051
 
2052
class procedure TRecycleBinManager.RecyclerEmptyEventGetSoundCategories(AStringList: TStringList);
2053
var
2054
  reg: TRegistry;
2055
begin
2056
  reg := TRegistry.Create;
2057
  try
2058
    reg.RootKey := HKEY_CURRENT_USER;
2059
    if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
2060
    begin
2061
      reg.GetKeyNames(AStringList);
2062
      reg.CloseKey;
2063
    end;
2064
  finally
2065
    reg.Free;
2066
  end;
2067
end;
2068
 
2069
class function TRecycleBinManager.RecyclerEmptyEventGetSound(ACategory: string): string;
2070
var
2071
  reg: TRegistry;
2072
resourcestring
2073
  LNG_SND_EVENT_CAT_ERROR = 'The category "%s" is not available for the notification event "%s".';
2074
begin
2075
  // Outputs an filename or empty string for no sound defined.
2076
 
2077
  reg := TRegistry.Create;
2078
  try
2079
    reg.RootKey := HKEY_CURRENT_USER;
2080
    if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
2081
    begin
2082
      if reg.OpenKeyReadOnly(ACategory) then
2083
      begin
2084
        result := reg.ReadString('');
2085
        reg.CloseKey;
2086
      end
2087
      else
2088
        raise EEventCategoryNotDefined.CreateFmt(LNG_SND_EVENT_CAT_ERROR, [ACategory, 'EmptyRecycleBin']);
2089
      reg.CloseKey;
2090
    end;
2091
  finally
2092
    reg.Free;
2093
  end;
2094
end;
2095
 
2096
class function TRecycleBinManager.RecyclerQueryFunctionAvailable: boolean;
2097
var
2098
  RBHandle: THandle;
2099
  SHQueryRecycleBin: TSHQueryRecycleBin;
2100
begin
2101
  // Windows 95 without Internet Explorer 4 has no SHQueryRecycleBinA.
2102
  RBHandle := LoadLibrary(shell32);
2103
  try
2104
    if RBHandle <> 0 then
2105
    begin
2106
      SHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
2107
      if not Assigned(@SHQueryRecycleBin) then
2108
      begin
2109
        FreeLibrary(RBHandle);
2110
        RBHandle := 0;
2111
      end;
2112
    end;
2113
 
2114
    result := RBHandle <> 0;
2115
  finally
2116
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2117
  end;
2118
end;
2119
 
2120
class function TRecycleBinManager.RecyclerAddFileOrFolder(FileOrFolder: string; confirmation: boolean=false): boolean;
2121
var
2122
  Operation: TSHFileOpStruct;
2123
begin
2124
  // Template: http://www.dsdt.info/tipps/?id=116
2125
  with Operation do
2126
  begin
2127
    Wnd := hInstance; // OK?
2128
    wFunc := FO_DELETE;
2129
    pFrom := PChar(FileOrFolder + #0);
2130
    pTo := nil;
2131
    fFlags := FOF_ALLOWUNDO;
2132
    if not confirmation then fFlags := fFlags or FOF_NOCONFIRMATION;
2133
  end;
2134
  Result := SHFileOperation(Operation) = 0;
2135
end;
2136
 
2137
class function TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles: GPOLICYBOOL;
2138
var
2139
  reg: TRegistry;
2140
begin
2141
  result := gpUndefined;
2142
 
2143
  reg := TRegistry.Create;
2144
  try
2145
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2146
    // even if gpedit.msc shows "Not configured"!
2147
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2148
    reg.RootKey := HKEY_LOCAL_MACHINE;
2149
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2150
    begin
2151
      if reg.ValueExists('NoRecycleFiles') then
2152
      begin
2153
        if reg.ReadBool('NoRecycleFiles') then
2154
          result := gpEnabled
2155
        else
2156
          result := gpDisabled;
2157
        Exit;
2158
      end;
2159
      reg.CloseKey;
2160
    end;
2161
    {$ENDIF}
2162
 
2163
    reg.RootKey := HKEY_CURRENT_USER;
2164
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2165
    begin
2166
      if reg.ValueExists('NoRecycleFiles') then
2167
      begin
2168
        if reg.ReadBool('NoRecycleFiles') then
2169
          result := gpEnabled
2170
        else
2171
          result := gpDisabled;
2172
      end;
2173
      reg.CloseKey;
2174
    end;
2175
  finally
2176
    reg.Free;
2177
  end;
2178
end;
2179
 
2180
class function TRecycleBinManager.RecyclerGroupPolicyConfirmFileDelete: GPOLICYBOOL;
2181
var
2182
  reg: TRegistry;
2183
begin
2184
  result := gpUndefined;
2185
  reg := TRegistry.Create;
2186
  try
2187
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2188
    // even if gpedit.msc shows "Not configured"!
2189
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2190
    reg.RootKey := HKEY_LOCAL_MACHINE;
2191
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2192
    begin
2193
      if reg.ValueExists('ConfirmFileDelete') then
2194
      begin
2195
        if reg.ReadBool('ConfirmFileDelete') then
2196
          result := gpEnabled
2197
        else
2198
          result := gpDisabled;
2199
        Exit;
2200
      end;
2201
      reg.CloseKey;
2202
    end;
2203
    {$ENDIF}
2204
 
2205
    reg.RootKey := HKEY_CURRENT_USER;
2206
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2207
    begin
2208
      if reg.ValueExists('ConfirmFileDelete') then
2209
      begin
2210
        if reg.ReadBool('ConfirmFileDelete') then
2211
          result := gpEnabled
2212
        else
2213
          result := gpDisabled;
2214
      end;
2215
      reg.CloseKey;
2216
    end;
2217
  finally
2218
    reg.Free;
2219
  end;
2220
end;
2221
 
2222
class function TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize: integer;
2223
var
2224
  reg: TRegistry;
2225
begin
2226
  result := -1;
2227
  reg := TRegistry.Create;
2228
  try
2229
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2230
    // even if gpedit.msc shows "Not configured"!
2231
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2232
    reg.RootKey := HKEY_LOCAL_MACHINE;
2233
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2234
    begin
2235
      if reg.ValueExists('RecycleBinSize') then
2236
      begin
2237
        result := reg.ReadInteger('RecycleBinSize');
2238
        Exit;
2239
      end;
2240
      reg.CloseKey;
2241
    end;
2242
    {$ENDIF}
2243
 
2244
    reg.RootKey := HKEY_CURRENT_USER;
2245
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2246
    begin
2247
      if reg.ValueExists('RecycleBinSize') then
2248
      begin
2249
        result := reg.ReadInteger('RecycleBinSize');
2250
      end;
2251
      reg.CloseKey;
2252
    end;
2253
  finally
2254
    reg.Free;
2255
  end;
2256
end;
2257
 
2258
class function TRecycleBinManager.RecyclerConfirmationDialogEnabled: boolean;
2259
var
2260
  gp: GPOLICYBOOL;
2261
begin
2262
  gp := RecyclerGroupPolicyConfirmFileDelete;
2263
  if gp <> gpUndefined then
2264
  begin
2265
    result := gp = gpEnabled;
2266
  end
2267
  else
2268
  begin
2269
    result := RecyclerShellStateConfirmationDialogEnabled;
2270
  end;
2271
end;
2272
 
2273
class function TRecycleBinManager.RecyclerShellStateConfirmationDialogEnabled: boolean;
2274
var
2275
  lpss: SHELLSTATE;
2276
  bNoConfirmRecycle: boolean;
2277
 
2278
  PSHGetSettings: TSHGetSettings;
2279
  RBHandle: THandle;
2280
 
2281
  reg: TRegistry;
2282
  rbuf: array[0..255] of byte;
2283
begin
2284
  PSHGetSettings := nil;
2285
  result := false; // Avoid warning message
2286
 
2287
  RBHandle := LoadLibrary(shell32);
2288
  try
2289
    if RBHandle <> 0 then
2290
    begin
2291
      PSHGetSettings := GetProcAddress(RBHandle, C_SHGetSettings);
2292
      if not Assigned(@PSHGetSettings) then
2293
      begin
2294
        FreeLibrary(RBHandle);
2295
        RBHandle := 0;
2296
      end;
2297
    end;
2298
 
2299
    if (RBHandle <> 0) and Assigned(PSHGetSettings) then
2300
    begin
2301
      ZeroMemory(@lpss, SizeOf(lpss));
2302
      PSHGetSettings(lpss, SSF_NOCONFIRMRECYCLE);
2303
      bNoConfirmRecycle := (lpss.Flags1 and 4) = 4; // fNoConfirmRecycle
2304
 
2305
      result := not bNoConfirmRecycle;
2306
    end
2307
    else
2308
    begin
2309
      reg := TRegistry.Create;
2310
      try
2311
        // API function call failed. Probably because Windows is too old.
2312
        // Try to read out from registry.
2313
        // The 3rd bit of the 5th byte of "ShellState" is the value
2314
        // of "fNoConfirmRecycle".
2315
 
2316
        reg.RootKey := HKEY_CURRENT_USER;
2317
        if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer') then
2318
        begin
2319
          ZeroMemory(@rbuf, SizeOf(rbuf));
2320
          reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf));
2321
 
2322
          // Lese 3tes Bit vom 5ten Byte
2323
          bNoConfirmRecycle := ((rbuf[4] and 4) = 4);
2324
          result := not bNoConfirmRecycle;
2325
 
2326
          reg.CloseKey;
2327
        end
2328
        else
2329
        begin
2330
          raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSettings])]);
2331
        end;
2332
      finally
2333
        reg.Free;
2334
      end;
2335
    end;
2336
  finally
2337
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2338
  end;
2339
end;
2340
 
2341
class procedure TRecycleBinManager.RecyclerConfirmationDialogSetEnabled(NewSetting: boolean);
2342
var
2343
  lpss: SHELLSTATE;
2344
 
2345
  PSHGetSetSettings: TSHGetSetSettings;
2346
  RBHandle: THandle;
2347
 
2348
  reg: TRegistry;
2349
  rbuf: array[0..255] of byte;
2350
 
89 daniel-mar 2351
  //dwResult: DWORD;
2352
  lpdwResult: PDWORD_PTR;
75 daniel-mar 2353
begin
2354
  PSHGetSetSettings := nil;
89 daniel-mar 2355
  lpdwResult := nil;
75 daniel-mar 2356
 
2357
  RBHandle := LoadLibrary(shell32);
2358
  try
2359
    if RBHandle <> 0 then
2360
    begin
2361
      PSHGetSetSettings := GetProcAddress(RBHandle, C_SHGetSetSettings);
2362
      if not Assigned(@PSHGetSetSettings) then
2363
      begin
2364
        FreeLibrary(RBHandle);
2365
        RBHandle := 0;
2366
      end;
2367
    end;
2368
 
2369
    if (RBHandle <> 0) and Assigned(PSHGetSetSettings) then
2370
    begin
2371
      ZeroMemory(@lpss, SizeOf(lpss));
2372
 
2373
      PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, false); // Get
2374
 
2375
      // Set 3rd bit equal to NewSetting
2376
      if NewSetting then
2377
        lpss.Flags1 := lpss.Flags1 or  $00000004
2378
      else
2379
        lpss.Flags1 := lpss.Flags1 and $FFFFFFFB;
2380
 
2381
      PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, true); // Set
2382
 
2383
      SendMessageTimeout (
2384
        HWND_BROADCAST, WM_SETTINGCHANGE,
2385
        0, lParam (pChar ('ShellState')),
89 daniel-mar 2386
        SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
75 daniel-mar 2387
      );
2388
    end
2389
    else
2390
    begin
2391
      reg := TRegistry.Create;
2392
      try
2393
        // API function call failed. Probably because Windows is too old.
2394
        // Try to read out from registry.
2395
        // The 3rd bit of the 5th byte of "ShellState" is the value
2396
        // of "fNoConfirmRecycle".
2397
 
2398
        reg.RootKey := HKEY_CURRENT_USER;
2399
        if reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer', false) then
2400
        begin
2401
          ZeroMemory(@rbuf, SizeOf(rbuf));
2402
 
2403
          reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Get
2404
 
2405
          // Set 3rd bit equal to NewSetting
2406
          if NewSetting then
2407
            rbuf[4] := rbuf[4] or  $04
2408
          else
2409
            rbuf[4] := rbuf[4] and $FB;
2410
 
2411
          reg.WriteBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Set
2412
 
2413
          SendMessageTimeout (
2414
            HWND_BROADCAST, WM_SETTINGCHANGE,
2415
            0, lParam (pChar ('ShellState')),
89 daniel-mar 2416
            SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
75 daniel-mar 2417
          );
2418
 
2419
          reg.CloseKey;
2420
        end
2421
        else
2422
        begin
2423
          raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSetSettings])]);
2424
        end;
2425
      finally
2426
        reg.Free;
2427
      end;
2428
    end;
2429
  finally
2430
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2431
  end;
2432
end;
2433
 
2434
{ TRbRecycleBinItem }
2435
 
2436
function TRbRecycleBinItem.GetSource: string;
2437
begin
2438
  {$IFDEF UNICODE}
2439
  result := SourceUnicode;
2440
  {$ELSE}
2441
  result := SourceAnsi;
2442
  {$ENDIF}
2443
end;
2444
 
2445
end.