Subversion Repositories recyclebinunit

Rev

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