Subversion Repositories recyclebinunit

Rev

Rev 102 | 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
////////////////////////////////////////////////////////////////////////////////////
106 daniel-mar 8
// Revision: 04 JUN 2023                                                          //
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
106 daniel-mar 58
  RECBINUNIT_VERSION = '2023-06-04';
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
102 daniel-mar 118
    constructor Create(fs: TStream; AIndexFile: string); reintroduce;
75 daniel-mar 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
102 daniel-mar 129
    constructor Create(fs: TStream; AIndexFile: string); reintroduce;
75 daniel-mar 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
102 daniel-mar 140
    constructor Create(fs: TStream; AIndexFile, AID: string); reintroduce;
75 daniel-mar 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);
100 daniel-mar 840
          if (Copy(testVistaItem.Source,2,2) <> ':\') and
841
             (Copy(testVistaItem.Source,2,2) <> '\\') then
99 daniel-mar 842
            FreeAndNil(testVistaItem);
843
        except
844
          testVistaItem := nil;
845
        end;
846
      end;
847
      {$ENDREGION}
848
 
849
      if Assigned(testVistaItem) then
850
      begin
851
        list.Add(testVistaItem);
852
      end
853
      else
854
      begin
855
        fs.Seek(0, soFromBeginning);
76 daniel-mar 856
        if TolerantReading then
857
        begin
858
          // This is a special treatment how to recover data from an INFO/INFO2 file
859
          // which was corrupted by an incompatible multiboot configuration.
860
          // Example:
861
          // - Win95 without IE4 and WinNT4 both write into the INFO file. But Win95 appends the ANSI record and WinNT appends an Unicode record.
862
          // - 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.
863
          fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
864
          while fs.Position < fs.size do
75 daniel-mar 865
          begin
76 daniel-mar 866
            // Can we actually read a Unicode record?
867
            if fs.Position + SizeOf(TRbInfoRecordW) <= fs.Size then
75 daniel-mar 868
            begin
76 daniel-mar 869
              // Try to read the Unicode record and check if it is valid
870
              // In case it is no Unicode record, then the Unicode part will be the
871
              // ANSI source name of the next record. In this case, we won't get
100 daniel-mar 872
              // a ':\' or '\\' at the Unicode string.
76 daniel-mar 873
              bakPosition := fs.Position;
874
              wTest := TRbInfoWItem.Create(fs, AFile);
100 daniel-mar 875
              if (Copy(wTest.SourceUnicode, 2, 2) = ':\') or
876
                 (Copy(wTest.SourceUnicode, 2, 2) = '\\') then
76 daniel-mar 877
              begin
878
                // Yes, it is a valid Unicode record.
879
                list.Add(wTest);
880
              end
881
              else
882
              begin
883
                // No, it is not a valid Unicode record. Jump back, and we need
884
                // to assume that the following record will be a valid ANSI record.
885
                fs.Position := bakPosition;
886
                list.Add(TRbInfoAItem.Create(fs, AFile));
887
              end;
888
            end
99 daniel-mar 889
            else if fs.Position + SizeOf(TRbInfoRecordA) <= fs.Size then
76 daniel-mar 890
            begin
891
              // No, there is not enough space left for an Unicode record.
892
              // So we assume that the following record will be a valid ANSI record.
893
              list.Add(TRbInfoAItem.Create(fs, AFile));
99 daniel-mar 894
            end
895
            else
896
            begin
101 daniel-mar 897
              // Not enough space to read a ANSI record!
99 daniel-mar 898
              // Ignore it
75 daniel-mar 899
            end;
900
          end;
76 daniel-mar 901
        end
902
        else
903
        begin
904
          // This is the code for non-tolerant reading of the records.
905
          fs.ReadBuffer(infoHdr, SizeOf(infoHdr));
906
          case infoHdr.recordLength of
907
            SizeOf(TRbInfoRecordA):
75 daniel-mar 908
            begin
76 daniel-mar 909
              while fs.Position < fs.size do
910
              begin
911
                list.Add(TRbInfoAItem.Create(fs, AFile));
912
              end;
75 daniel-mar 913
            end;
76 daniel-mar 914
            SizeOf(TRbInfoRecordW):
915
            begin
916
              while fs.Position < fs.size do
917
              begin
918
                list.Add(TRbInfoWItem.Create(fs, AFile));
919
              end;
920
            end
921
            else
922
            begin
923
              raise Exception.CreateFmt(LNG_INVALID_INFO_FORMAT, [AFile]);
924
            end;
75 daniel-mar 925
          end;
926
        end;
927
      end;
928
    finally
929
      FreeAndNil(fs);
930
    end;
931
  end;
932
 
106 daniel-mar 933
  // TODO: Nice would be some progress callback function
75 daniel-mar 934
  procedure _HandleVistaDir(ADirectory: string);
935
  var
936
    SR: TSearchRec;
937
    fs: TFileStream;
938
    id: string;
939
  begin
940
    ADirectory := IncludeTrailingPathDelimiter(ADirectory);
941
 
106 daniel-mar 942
    //if FindFirst(ADirectory + '$I*', faAnyFile, SR) = 0 then
943
    if FindFirst(ADirectory + '*', faAnyFile, SR) = 0 then
75 daniel-mar 944
    begin
945
      repeat
106 daniel-mar 946
        // FindFirst filter '$I*' finds the file '$RJK3R4P.~43~' and '$RJB99A3.~55~' (yes, $R !!!) Why?!
947
        // ... so, we just filter for '*' and try to find the '$I*' files ourselves...
948
        if UpperCase(Copy(sr.Name,1,2)) = '$I' then
949
        begin
950
          id := sr.Name;
951
          { 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
952
          id := Copy(id, 3, Length(id)-2);
75 daniel-mar 953
 
106 daniel-mar 954
          fs := TFileStream.Create(ADirectory+sr.Name, fmOpenRead);
955
          try
956
            fs.Seek(0, soFromBeginning);
957
            list.Add(TRbVistaItem.Create(fs, ADirectory+sr.Name, id));
958
          finally
959
            FreeAndNil(fs);
960
          end;
75 daniel-mar 961
        end;
962
      until FindNext(SR) <> 0;
963
    end;
964
    FindClose(SR);
965
  end;
966
 
967
begin
968
  if DirectoryExists(FFileOrDirectory) then // Vista, as well as directories with INFO and INFO2
969
  begin
970
    _HandleVistaDir(FFileOrDirectory);
971
 
972
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2') then
973
    begin
974
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO2');
975
    end;
976
 
977
    if FileExists(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO') then
978
    begin
979
      _HandleIndexFile(IncludeTrailingPathDelimiter(FFileOrDirectory) + 'INFO');
980
    end;
981
  end
982
  else if FileExists(FFileOrDirectory) then
983
  begin
76 daniel-mar 984
    _HandleIndexFile(FFileOrDirectory); // Either INFO, or INFO2, or a single Vista index file
75 daniel-mar 985
  end
986
  else raise Exception.CreateFmt(LNG_FILE_NOT_FOUND, [FFileOrDirectory]);
987
end;
988
 
989
{ TRbDrive }
990
 
991
procedure TRbDrive.CheckDriveExisting;
992
begin
993
  // Does the drive exist?
994
  // see http://www.delphipraxis.net/post2933.html
78 daniel-mar 995
  if not GetLogicalDrives and (1 shl DriveNumber) <> 0 then
75 daniel-mar 996
  begin
89 daniel-mar 997
    raise EInvalidDrive.CreateFmt(LNG_DRIVE_NOT_EXISTING, [UpperCase(string(FDriveLetter))+':']);
75 daniel-mar 998
  end;
999
end;
1000
 
89 daniel-mar 1001
constructor TRbDrive.Create(ADriveLetter: AnsiChar);
75 daniel-mar 1002
begin
1003
  inherited Create;
1004
 
1005
  FDriveLetter := ADriveLetter;
1006
  CheckDriveExisting;
1007
end;
1008
 
78 daniel-mar 1009
function TRbDrive.DiskSize: integer;
1010
begin
1011
  result := SysUtils.DiskSize(DriveNumber+1 {0 is current, 1 is A}) div (1024*1024);
1012
end;
1013
 
1014
function TRbDrive.DriveNumber: integer;
1015
begin
1016
  result := DriveLetterToDriveNumber(FDriveLetter);
1017
end;
1018
 
75 daniel-mar 1019
function TRbDrive.GetAPIInfo: TSHQueryRBInfo;
1020
var
1021
  PSHQueryRecycleBin: TSHQueryRecycleBin;
1022
  RBHandle: THandle;
1023
  res: HRESULT;
1024
  Path: string;
1025
begin
1026
  Path := FDriveLetter + ':\';
1027
 
1028
  // Ref: http://www.delphipraxis.net/post1291.html
1029
 
1030
  RBHandle := LoadLibrary(shell32);
1031
  try
1032
    PSHQueryRecycleBin := nil;
1033
    if RBHandle <> 0 then
1034
    begin
1035
      PSHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
1036
      if not Assigned(@PSHQueryRecycleBin) then
1037
      begin
1038
        FreeLibrary(RBHandle);
1039
        RBHandle := 0;
1040
      end;
1041
    end;
1042
 
1043
    FillChar(result, SizeOf(TSHQueryRBInfo), 0);
1044
    result.cbSize := SizeOf(TSHQueryRBInfo);
1045
 
1046
    if (RBHandle <> 0) and Assigned(PSHQueryRecycleBin) then
1047
    begin
1048
      res := PSHQueryRecycleBin(PChar(Path), result);
1049
      // if Succeeded(res) then
1050
      if res = S_OK then
1051
      begin
1052
        // Alles OK, unser result hat nun die gewünschten Daten.
1053
      end
1054
      else
1055
      begin
1056
        // Since Windows Vista, SHQueryRecycleBin will fail with E_FAIL (80004005)
1057
        // if Path is a floppy or CD drive...
1058
        raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_ERROR_CODE, [C_SHQueryRecycleBin, Path, '0x'+IntToHex(res, 2*SizeOf(HRESULT))])]);
1059
      end;
1060
    end
1061
    else
1062
      raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHQueryRecycleBin])]);
1063
  finally
1064
    if RBHandle <> 0 then FreeLibrary(RBHandle);
1065
  end;
1066
end;
1067
 
78 daniel-mar 1068
function TRbDrive.GetMaxPercentUsage: Extended;
75 daniel-mar 1069
var
78 daniel-mar 1070
  abs: integer; // in MB
1071
  rel: integer; // in % (0-100)
1072
  gpSetting: integer;
1073
const
1074
  DEFAULT_PERCENT = 10; // Windows 95 default
1075
begin
1076
  gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
1077
  if gpSetting <> -1 then
1078
    result := gpSetting / 100
1079
  else if TRecycleBinManager.UsesGlobalSettings then
1080
    result := TRecycleBinManager.GetGlobalMaxPercentUsage / 100
1081
  else if OldCapacityPercent(rel) then
1082
  begin
1083
    result := rel / 100;
1084
  end
1085
  else if NewCapacityAbsolute(abs) then
1086
  begin
1087
    result := abs / DiskSize;
1088
  end
1089
  else
1090
  begin
1091
    result := DEFAULT_PERCENT / 100;
1092
  end;
1093
end;
1094
 
1095
function TRbDrive.GetMaxAbsoluteUsage: integer;
1096
var
1097
  abs: integer; // in MB
1098
  rel: integer; // in % (0-100)
1099
  gpSetting: integer;
1100
const
1101
  DEFAULT_PERCENT = 10; // Windows 95 default
1102
begin
1103
  gpSetting := TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize;
1104
  if gpSetting <> -1 then
1105
    result := Ceil(gpSetting/100 * DiskSize)
1106
  else if TRecycleBinManager.UsesGlobalSettings then
1107
    result := Ceil(TRecycleBinManager.GetGlobalMaxPercentUsage/100 * DiskSize)
1108
  else if NewCapacityAbsolute(abs) then
1109
  begin
1110
    result := abs;
1111
  end
1112
  else if OldCapacityPercent(rel) then
1113
  begin
1114
    result := Ceil(rel/100 * DiskSize);
1115
  end
1116
  else
1117
  begin
1118
    result := Ceil(DEFAULT_PERCENT/100 * DiskSize);
1119
  end;
1120
end;
1121
 
1122
function TRbDrive.OldCapacityPercent(var res: integer): boolean;
1123
var
75 daniel-mar 1124
  reg: TRegistry;
1125
  purgeInfo: TRbWin95PurgeInfo;
1126
begin
78 daniel-mar 1127
  if Win32MajorVersion >= 6 then
1128
  begin
1129
    // Only available till Windows XP
1130
    result := false;
1131
    exit;
1132
  end;
75 daniel-mar 1133
 
78 daniel-mar 1134
  result := false;
1135
 
75 daniel-mar 1136
  reg := TRegistry.Create;
1137
  try
1138
    reg.RootKey := HKEY_LOCAL_MACHINE;
1139
 
1140
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1141
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1142
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1143
    begin
89 daniel-mar 1144
      if reg.OpenKeyReadOnly(string(FDriveLetter)) then
75 daniel-mar 1145
      begin
1146
        if reg.ValueExists('Percent') then
1147
        begin
1148
          // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1149
 
78 daniel-mar 1150
          res := reg.ReadInteger('Percent');
1151
          result := true;
75 daniel-mar 1152
        end;
1153
      end
1154
      else
1155
      begin
1156
        if reg.ValueExists('PurgeInfo') then
1157
        begin
1158
          // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1159
 
1160
          reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1161
 
78 daniel-mar 1162
          res := purgeInfo.percentDrive[FDriveLetter];
1163
          result := true;
75 daniel-mar 1164
        end;
1165
      end;
1166
 
1167
      reg.CloseKey;
1168
    end;
1169
  finally
1170
    reg.Free;
1171
  end;
1172
end;
1173
 
78 daniel-mar 1174
function TRbDrive.NewCapacityAbsolute(var res: integer): boolean;
75 daniel-mar 1175
var
78 daniel-mar 1176
  reg: TRegistry;
75 daniel-mar 1177
begin
78 daniel-mar 1178
  if Win32MajorVersion < 6 then
1179
  begin
1180
    // Only available since Windows Vista
1181
    result := false;
1182
    exit;
1183
  end;
75 daniel-mar 1184
 
78 daniel-mar 1185
  result := false;
1186
 
1187
  reg := TRegistry.Create;
1188
  try
1189
    reg.RootKey := HKEY_CURRENT_USER;
1190
 
1191
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
1192
    begin
1193
      // Windows Vista and upwards
1194
      if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
1195
      begin
1196
        res := reg.ReadInteger('MaxCapacity'); // in MB
1197
        result := true;
1198
      end;
1199
      reg.CloseKey;
1200
    end;
1201
  finally
1202
    reg.Free;
1203
  end;
75 daniel-mar 1204
end;
1205
 
1206
function TRbDrive.GetNukeOnDelete: boolean;
1207
var
1208
  reg: TRegistry;
1209
  purgeInfo: TRbWin95PurgeInfo;
1210
const
78 daniel-mar 1211
  RES_DEFAULT = false; // Windows 95 default
75 daniel-mar 1212
begin
78 daniel-mar 1213
  if TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles = gpEnabled then
1214
    result := true
1215
  else if TRecycleBinManager.UsesGlobalSettings then
1216
    result := TRecycleBinManager.GetGlobalNukeOnDelete
1217
  else
1218
  begin
1219
    result := RES_DEFAULT;
75 daniel-mar 1220
 
78 daniel-mar 1221
    reg := TRegistry.Create;
1222
    try
1223
      reg.RootKey := HKEY_CURRENT_USER;
75 daniel-mar 1224
 
78 daniel-mar 1225
      if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume') then
75 daniel-mar 1226
      begin
78 daniel-mar 1227
        // Windows Vista and upwards
1228
        if reg.OpenKeyReadOnly(GUIDToString(VolumeGUID)) then
75 daniel-mar 1229
        begin
1230
          result := reg.ReadBool('NukeOnDelete');
1231
        end;
78 daniel-mar 1232
        reg.CloseKey;
75 daniel-mar 1233
      end
1234
      else
1235
      begin
78 daniel-mar 1236
        reg.RootKey := HKEY_LOCAL_MACHINE;
1237
 
1238
        // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1239
        // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1240
        if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
75 daniel-mar 1241
        begin
89 daniel-mar 1242
          if reg.OpenKeyReadOnly(string(FDriveLetter)) then
78 daniel-mar 1243
          begin
1244
            if reg.ValueExists('NukeOnDelete') then
1245
            begin
1246
              // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
75 daniel-mar 1247
 
78 daniel-mar 1248
              result := reg.ReadBool('NukeOnDelete');
1249
            end;
1250
          end
1251
          else
1252
          begin
1253
            if reg.ValueExists('PurgeInfo') then
1254
            begin
1255
              // Windows 95 - Kodierte Informationen liegen in PurgeInfo
75 daniel-mar 1256
 
78 daniel-mar 1257
              reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1258
 
1259
              result := ((purgeInfo.NukeOnDeleteBits shr DriveNumber) and 1) = 1;
1260
            end;
1261
          end;
1262
 
1263
          reg.CloseKey;
75 daniel-mar 1264
        end;
1265
      end;
78 daniel-mar 1266
    finally
1267
      reg.Free;
75 daniel-mar 1268
    end;
1269
  end;
1270
end;
1271
 
1272
function TRbDrive.GetNumItems: int64;
1273
begin
1274
  result := GetAPIInfo.i64NumItems;
1275
end;
1276
 
1277
function TRbDrive.GetSize: int64;
1278
begin
1279
  result := GetAPIInfo.i64Size;
1280
end;
1281
 
1282
function TRbDrive.GetVolumeGUID: TGUID;
1283
begin
1284
  if GetDriveGUID(FDriveLetter, result) <> ERROR_SUCCESS then
1285
  begin
1286
    result := NULL_GUID;
1287
  end;
1288
end;
1289
 
77 daniel-mar 1290
function TRbDrive.GetVolumeGUIDAvailable: boolean;
1291
begin
1292
  result := not IsEqualGUID(VolumeGUID, NULL_GUID);
1293
end;
1294
 
75 daniel-mar 1295
function TRbDrive.IsEmpty: boolean;
1296
begin
1297
  result := GetNumItems = 0;
1298
end;
1299
 
1300
function TRbDrive.IsFAT: boolean;
1301
var
1302
  Dummy2: DWORD;
1303
  Dummy3: DWORD;
99 daniel-mar 1304
  FileSystem: array[0..MAX_PATH-1] of char;
1305
  VolumeName: array[0..MAX_PATH-1] of char;
75 daniel-mar 1306
  s: string;
1307
begin
1308
  s := FDriveLetter + DriveDelim + PathDelim; // ohne die Auslagerung in einen String kommt es zu einer AV in ntdll
1309
  GetVolumeInformation(PChar(s), VolumeName,
1310
    SizeOf(VolumeName), nil, Dummy2, Dummy3, FileSystem, SizeOf(FileSystem));
1311
  result := uppercase(copy(FileSystem, 0, 3)) = 'FAT';
1312
end;
1313
 
1314
procedure TRbDrive.ListRecycleBins(list: TObjectList{TRbRecycleBin}; UserSID: string='');
1315
 
1316
  procedure _AddSIDFolders(dir: string; wholeFolder: boolean);
1317
  var
1318
    SR: TSearchRec;
1319
  begin
1320
    dir := IncludeTrailingPathDelimiter(dir);
1321
    if FindFirst(dir+'S-*', faAnyFile, SR) = 0 then
1322
    begin
1323
      try
1324
        repeat
1325
          if (SR.Name = '.') or (SR.Name = '..') or not DirectoryExists(dir + SR.Name) then continue;
1326
 
1327
          if wholeFolder then
1328
          begin
1329
            // Vista
1330
            list.Add(TRbRecycleBin.Create(dir+SR.Name, SR.Name));
1331
          end
1332
          else
1333
          begin
1334
            // Win95 .. WinXP
1335
            if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2') then
1336
              list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO2', SR.Name));
1337
            if FileExists(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO') then
1338
              list.Add(TRbRecycleBin.Create(IncludeTrailingPathDelimiter(dir+SR.Name) + 'INFO', SR.Name));
1339
          end;
1340
        until FindNext(SR) <> 0;
1341
      finally
1342
        FindClose(SR);
1343
      end;
1344
    end;
1345
  end;
1346
 
1347
var
1348
  dir: string;
1349
begin
1350
  // Find recyclers from Windows Vista or higher
1351
 
1352
  if IsFAT then
1353
  begin
1354
    dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim;
1355
    if DirectoryExists(dir) then
1356
    begin
1357
      list.Add(TRbRecycleBin.Create(dir));
1358
    end;
1359
  end
1360
  else
1361
  begin
1362
    if UserSID <> '' then
1363
    begin
1364
      dir := FDriveLetter + DriveDelim + PathDelim + '$recycle.bin' + PathDelim + UserSID + PathDelim;
1365
      if DirectoryExists(dir) then
1366
      begin
1367
        list.Add(TRbRecycleBin.Create(dir, UserSID));
1368
      end;
1369
    end
1370
    else
1371
    begin
1372
      _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + '$recycle.bin', true);
1373
    end;
1374
  end;
1375
 
1376
  // Find recyclers from Windows before Vista
1377
 
1378
  if IsFAT then
1379
  begin
1380
    dir := FDriveLetter + DriveDelim + PathDelim + 'Recycled' + PathDelim;
1381
 
1382
    // Both "recycle bins" are possible if you have multiboot (but do overwrite themselfes if you empty them)
1383
    if FileExists(dir + 'INFO2') then
76 daniel-mar 1384
      list.Add(TRbRecycleBin.Create(dir + 'INFO2')); // Windows 95 with Internet Explorer 4 Extension or higher Windows 9x versions
75 daniel-mar 1385
    if FileExists(dir + 'INFO') then
1386
      list.Add(TRbRecycleBin.Create(dir + 'INFO')); // Windows 95 native
1387
  end
1388
  else
1389
  begin
1390
    if UserSID <> '' then
1391
    begin
1392
      dir := FDriveLetter + DriveDelim + PathDelim + 'Recycler' + PathDelim + UserSID + PathDelim;
1393
 
1394
      if FileExists(dir + 'INFO2') then
76 daniel-mar 1395
        list.Add(TRbRecycleBin.Create(dir + 'INFO2', UserSID)); // Windows 2000+
75 daniel-mar 1396
      if FileExists(dir + 'INFO') then
1397
        list.Add(TRbRecycleBin.Create(dir + 'INFO', UserSID)); // Windows NT 4
1398
    end
1399
    else
1400
    begin
1401
      _AddSIDFolders(FDriveLetter + DriveDelim + PathDelim + 'Recycler', false);
1402
    end;
1403
  end;
1404
end;
1405
 
76 daniel-mar 1406
{ TRbInfoAItem }
75 daniel-mar 1407
 
76 daniel-mar 1408
procedure TRbInfoAItem.ReadFromStream(stream: TStream);
75 daniel-mar 1409
var
76 daniel-mar 1410
  r: TRbInfoRecordA;
95 daniel-mar 1411
  i: Integer;
75 daniel-mar 1412
begin
1413
  stream.ReadBuffer(r, SizeOf(r));
1414
 
100 daniel-mar 1415
  if r.sourceDrive = 26 then
1416
    FSourceDrive := '@' // @ is the "Network home drive" of the Win95 time
1417
  else
1418
    FSourceDrive := Chr(Ord('A') + r.sourceDrive);
75 daniel-mar 1419
 
76 daniel-mar 1420
  // Win95 with IE4 and Win2000+:
1421
  // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
1422
  // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
1423
  // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
1424
  // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
1425
  // Zwecken eingesetzt werden soll.
75 daniel-mar 1426
  if r.sourceAnsi[0] = #0 then
1427
  begin
1428
    FRemovedEntry := true;
102 daniel-mar 1429
    if (r.sourceDrive = 26) and (r.sourceAnsi[1] = '\') then
1430
      r.sourceAnsi[0] := '\'
1431
    else
1432
      r.sourceAnsi[0] := AnsiChar(FSourceDrive);
75 daniel-mar 1433
  end;
1434
 
1435
  FSourceAnsi := r.sourceAnsi;
95 daniel-mar 1436
 
1437
  // Unicode does not exist in INFO(1) structure
1438
  (* FSourceUnicode := AnsiCharArrayToWideString(r.sourceAnsi); *)
1439
  SetLength(FSourceUnicode, Length(r.sourceAnsi));
1440
  for i := 0 to Length(r.sourceAnsi)-1 do
1441
    FSourceUnicode[i+1] := WideChar(r.sourceAnsi[i]);
1442
 
75 daniel-mar 1443
  FID := IntToStr(r.recordNumber);
1444
  FDeletionTime := FileTimeToDateTime(r.deletionTime);
1445
  FOriginalSize := r.originalSize;
88 daniel-mar 1446
 
1447
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1448
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1449
  AnsiRemoveNulChars(FSourceAnsi);
1450
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1451
end;
1452
 
76 daniel-mar 1453
function TRbInfoAItem.DeleteFile: boolean;
75 daniel-mar 1454
var
1455
  r: string;
1456
begin
1457
  r := GetPhysicalFile;
1458
  if DirectoryExists(r) then
1459
    result := DeleteDirectory(r) // Usually, the old recycle bin does not allow folders. Just to be sure, we include the code.
1460
  else
1461
    result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung? --> Win95: Funktioniert
1462
 
1463
  // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Datei neu schreiben)
1464
end;
1465
 
76 daniel-mar 1466
function TRbInfoAItem.GetPhysicalFile: string;
75 daniel-mar 1467
begin
1468
  if FRemovedEntry then
1469
  begin
1470
    result := '';
1471
    Exit;
1472
  end;
1473
 
1474
  // e.g. C:\...\DC0.doc
1475
  result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
89 daniel-mar 1476
            'D' + (* SourceDrive *) Source[1] + ID + ExtractFileExt(Source);
75 daniel-mar 1477
end;
1478
 
76 daniel-mar 1479
constructor TRbInfoAItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 1480
begin
1481
  inherited Create;
1482
  ReadFromStream(fs);
1483
  FIndexFile := AIndexFile;
1484
end;
1485
 
76 daniel-mar 1486
{ TRbInfoWItem }
75 daniel-mar 1487
 
76 daniel-mar 1488
procedure TRbInfoWItem.ReadFromStream(stream: TStream);
75 daniel-mar 1489
var
76 daniel-mar 1490
  r: TRbInfoRecordW;
75 daniel-mar 1491
begin
1492
  stream.ReadBuffer(r, SizeOf(r));
1493
 
76 daniel-mar 1494
  // Win95 with IE4 and Win2000+:
1495
  // Wenn ein Eintrag aus der INFO/INFO2 gelöscht wird, dann wird das erste Byte
1496
  // von sourceAnsi auf Null gesetzt, damit die ganze INFO/INFO2 Datei nicht
1497
  // ständig neu geschrieben werden muss (so wie es bei Win95 und WinNT4 der Fall war).
75 daniel-mar 1498
  // Wir lesen den Eintrag trotzdem, da unsere Software ja auch zu forensischen
1499
  // Zwecken eingesetzt werden soll.
1500
  if r.sourceAnsi[0] = #0 then
1501
  begin
1502
    FRemovedEntry := true;
1503
    r.sourceAnsi[0] := AnsiChar(r.sourceUnicode[0]);
102 daniel-mar 1504
    (*
1505
    if (r.sourceDrive = 26) and (r.sourceAnsi[1] = '\') then
1506
      r.sourceAnsi[0] := '\'
1507
    else
1508
      r.sourceAnsi[0] := AnsiChar(FSourceDrive);
1509
    *)
75 daniel-mar 1510
  end;
1511
 
1512
  FSourceAnsi := r.sourceAnsi;
1513
  FSourceUnicode := r.sourceUnicode;
1514
  FID := IntToStr(r.recordNumber);
100 daniel-mar 1515
 
1516
  if r.sourceDrive = 26 then
1517
    FSourceDrive := '@' // @ is the "Network home drive" of the Win95 time
1518
  else
1519
    FSourceDrive := Chr(Ord('A') + r.sourceDrive);
1520
 
75 daniel-mar 1521
  FDeletionTime := FileTimeToDateTime(r.deletionTime);
1522
  FOriginalSize := r.originalSize;
88 daniel-mar 1523
 
1524
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1525
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1526
  AnsiRemoveNulChars(FSourceAnsi);
1527
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1528
end;
1529
 
76 daniel-mar 1530
function TRbInfoWItem.DeleteFile: boolean;
75 daniel-mar 1531
var
1532
  r: string;
1533
begin
1534
  r := GetPhysicalFile;
1535
  if DirectoryExists(r) then
1536
    result := DeleteDirectory(r)
1537
  else
1538
    result := SysUtils.DeleteFile(r); // TODO: geht das oder gibt es zugriffsverletzung?
1539
 
1540
  // TODO: nun auch den eintrag aus der INFO-Datei rausschmeißen (Erstes Byte auf 0 setzen)
1541
end;
1542
 
76 daniel-mar 1543
function TRbInfoWItem.GetPhysicalFile: string;
75 daniel-mar 1544
begin
1545
  if FRemovedEntry then
1546
  begin
1547
    result := '';
1548
    Exit;
1549
  end;
1550
 
76 daniel-mar 1551
  (*
1552
  This is actually a bit tricky...
1553
  Win95 will choose the first letter of the AnsiSource name.
1554
  WinNT will choose the first letter of the UnicodeSource name.
1555
  WinXP will choose the driveNumber member.
1556
 
1557
  Windows XP is kinda buggy when it comes to changing a drive letter.
1558
  For example, the drive E: was changed to K:
1559
  The drive letter is 04 (E), the Source name begins with E:\ and the physical file is De0.txt .
1560
  After the recycle bin is opened the first time:
1561
  - The recycle bin will show the file origin as K:\ and not as E:\
1562
  - The file was renamed from De0.txt to Dk0.txt
1563
  - The file can be recovered at this time
1564
  When the recycle bin is closed, the INFO2 file will not be corrected (which is a bug).
1565
  So, if you open the recycle bin again, the record will be marked
1566
  as deleted in the INFO file (the first byte will be set to 0),
1567
  because Windows searches for De0.txt and doesn't find it.
1568
 
1569
  (This comment also applies to TRbInfoAItem.GetPhysicalFile)
1570
  *)
1571
 
75 daniel-mar 1572
  // e.g. C:\...\DC0.doc
1573
  result := IncludeTrailingPathDelimiter(ExtractFilePath(IndexFile)) +
76 daniel-mar 1574
            'D' + SourceDrive (* SourceUnicode[1] *) + ID + ExtractFileExt(SourceUnicode);
75 daniel-mar 1575
end;
1576
 
76 daniel-mar 1577
constructor TRbInfoWItem.Create(fs: TStream; AIndexFile: string);
75 daniel-mar 1578
begin
1579
  inherited Create;
1580
  ReadFromStream(fs);
1581
  FIndexFile := AIndexFile;
1582
end;
1583
 
1584
{ TRbVistaItem }
1585
 
1586
procedure TRbVistaItem.ReadFromStream(stream: TStream);
1587
var
82 daniel-mar 1588
  r1: TRbVistaRecord1;
1589
  r2: TRbVistaRecord2Head;
96 daniel-mar 1590
  r2SourceUnicode: array of WideChar;
106 daniel-mar 1591
  ver: int64;
95 daniel-mar 1592
  i: Integer;
96 daniel-mar 1593
resourcestring
106 daniel-mar 1594
  LNG_VISTA_WRONG_FORMAT = 'Invalid Vista index format version %d at file %s';
75 daniel-mar 1595
begin
106 daniel-mar 1596
  stream.ReadBuffer(ver, SizeOf(ver));
75 daniel-mar 1597
 
106 daniel-mar 1598
  if ver = 1 then
82 daniel-mar 1599
  begin
1600
    stream.Seek(0, soBeginning);
1601
    stream.ReadBuffer(r1, SizeOf(r1));
95 daniel-mar 1602
 
1603
    (* FSourceAnsi := AnsiString(WideCharArrayToWideString(r1.sourceUnicode)); *)
1604
    SetLength(FSourceAnsi, Length(r1.sourceUnicode));
1605
    for i := 0 to Length(r1.sourceUnicode)-1 do
1606
      FSourceAnsi[i+1] := AnsiChar(r1.sourceUnicode[i]); // Note: Invalid chars are automatically converted into '?'
1607
 
1608
    (* FSourceUnicode := WideCharArrayToWideString(r1.sourceUnicode); *)
1609
    SetLength(FSourceUnicode, Length(r1.sourceUnicode));
1610
    for i := 0 to Length(r1.sourceUnicode)-1 do
1611
      FSourceUnicode[i+1] := r1.sourceUnicode[i];
1612
 
82 daniel-mar 1613
    FID := ''; // will be added manually (at the constructor)
97 daniel-mar 1614
    FSourceDrive := Char(r1.sourceUnicode[1]);
82 daniel-mar 1615
    FDeletionTime := FileTimeToDateTime(r1.deletionTime);
1616
    FOriginalSize := r1.originalSize;
1617
  end
106 daniel-mar 1618
  else if ver = 2 then
82 daniel-mar 1619
  begin
1620
    stream.Seek(0, soBeginning);
1621
    stream.ReadBuffer(r2, SizeOf(r2));
1622
 
90 daniel-mar 1623
    SetLength(r2SourceUnicode, SizeOf(WideChar)*(r2.SourceCountChars-1));
1624
    stream.Read(r2SourceUnicode[0], SizeOf(WideChar)*(r2.sourceCountChars-1));
82 daniel-mar 1625
 
96 daniel-mar 1626
    // Invalid chars are automatically converted into '?'
1627
    (* FSourceAnsi := AnsiString(WideCharArrayToWideString(r2sourceUnicode)); *)
1628
    SetLength(FSourceAnsi, Length(r2sourceUnicode));
1629
    for i := 0 to Length(r2sourceUnicode)-1 do
1630
      FSourceAnsi[i+1] := AnsiChar(r2sourceUnicode[i]);
1631
 
1632
    (* FSourceUnicode := WideCharArrayToWideString(r2sourceUnicode); *)
1633
    SetLength(FSourceUnicode, Length(r2sourceUnicode));
1634
    for i := 0 to Length(r2sourceUnicode)-1 do
1635
      FSourceUnicode[i+1] := WideChar(r2sourceUnicode[i]);
1636
 
82 daniel-mar 1637
    FID := ''; // will be added manually (at the constructor)
97 daniel-mar 1638
    FSourceDrive := Char(r2sourceUnicode[1]);
82 daniel-mar 1639
    FDeletionTime := FileTimeToDateTime(r2.deletionTime);
1640
    FOriginalSize := r2.originalSize;
1641
  end
1642
  else
1643
  begin
106 daniel-mar 1644
    raise Exception.CreateFmt(LNG_VISTA_WRONG_FORMAT, [ver, FID]);
82 daniel-mar 1645
  end;
88 daniel-mar 1646
 
1647
  // Remove #0 at the end. There are some bugs where #0 is added to ANSI/Unicode read paths?! (probably in the ReadVista stuff)
1648
  // TODO: Instead of using this workaround, fix "SourceUnicode" and "SourceAnsi" in the first place!
1649
  AnsiRemoveNulChars(FSourceAnsi);
1650
  UnicodeRemoveNulChars(FSourceUnicode);
75 daniel-mar 1651
end;
1652
 
1653
function TRbVistaItem.DeleteFile: boolean;
1654
var
1655
  r: string;
1656
begin
1657
  r := GetPhysicalFile;
1658
  if DirectoryExists(r) then
1659
    result := DeleteDirectory(r)
1660
  else
1661
    result := SysUtils.DeleteFile(r);
1662
 
1663
  SysUtils.DeleteFile(FIndexFile);
1664
end;
1665
 
1666
function TRbVistaItem.GetPhysicalFile: string;
1667
begin
1668
  result := FIndexFile;
99 daniel-mar 1669
  if Pos('$I', Result) = 0 then
1670
    result := ''
1671
  else
1672
    result := StringReplace(Result, '$I', '$R', [rfIgnoreCase]);
75 daniel-mar 1673
end;
1674
 
1675
constructor TRbVistaItem.Create(fs: TStream; AIndexFile, AID: string);
1676
begin
1677
  inherited Create;
1678
  FIndexFile := AIndexFile;
1679
  FID := AID;
106 daniel-mar 1680
  ReadFromStream(fs);
75 daniel-mar 1681
end;
1682
 
1683
{ TRecycleBinManager }
1684
 
1685
class function TRecycleBinManager.EmptyOwnRecyclers(flags: cardinal): boolean;
1686
var
1687
  PSHEmptyRecycleBin: TSHEmptyRecycleBin;
1688
  LibHandle: THandle;
1689
begin
1690
  // Source: http://www.dsdt.info/tipps/?id=176
1691
  result := true;
1692
  LibHandle := LoadLibrary(shell32);
1693
  try
1694
    if LibHandle <> 0 then
1695
    begin
93 daniel-mar 1696
      @PSHEmptyRecycleBin := GetProcAddress(LibHandle, C_SHEmptyRecycleBin);
75 daniel-mar 1697
      if @PSHEmptyRecycleBin <> nil then
1698
      begin
1699
        PSHEmptyRecycleBin(hInstance, nil, flags);
1700
      end
1701
      else
1702
        result := false;
1703
    end
1704
    else
1705
      result := false;
1706
  finally
1707
    @PSHEmptyRecycleBin := nil;
1708
    if LibHandle <> 0 then FreeLibrary(LibHandle);
1709
  end;
1710
end;
1711
 
1712
class function TRecycleBinManager.EmptyOwnRecyclers(sound, progress, confirmation: boolean): boolean;
1713
const
1714
  SHERB_NOCONFIRMATION = $00000001;
1715
  SHERB_NOPROGRESSUI   = $00000002;
1716
  SHERB_NOSOUND        = $00000004;
1717
var
1718
  flags: cardinal;
1719
begin
1720
  flags := 0;
1721
 
1722
  if not progress then
1723
    flags := flags or SHERB_NOPROGRESSUI;
1724
  if not confirmation then
1725
    flags := flags or SHERB_NOCONFIRMATION;
1726
  if not sound then
1727
    flags := flags or SHERB_NOSOUND;
1728
 
1729
  result := EmptyOwnRecyclers(flags);
1730
end;
1731
 
1732
class function TRecycleBinManager.GetGlobalMaxPercentUsage: integer;
1733
var
1734
  reg: TRegistry;
1735
  purgeInfo: TRbWin95PurgeInfo;
1736
const
1737
  RES_DEFAULT = 10; // Windows 95 - Standardwert
1738
begin
78 daniel-mar 1739
  if Win32MajorVersion >= 6 then
1740
  begin
1741
    // Only available till Windows XP
1742
    result := -1;
1743
    exit;
1744
  end;
1745
 
75 daniel-mar 1746
  result := RES_DEFAULT;
1747
 
1748
  reg := TRegistry.Create;
1749
  try
1750
    reg.RootKey := HKEY_LOCAL_MACHINE;
1751
 
1752
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1753
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1754
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1755
    begin
1756
      if reg.ValueExists('Percent') then
1757
      begin
1758
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1759
 
1760
        result := reg.ReadInteger('Percent');
1761
      end
1762
      else if reg.ValueExists('PurgeInfo') then
1763
      begin
1764
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1765
 
1766
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1767
        result := purgeInfo.percentGlobal;
1768
      end;
1769
 
1770
      reg.CloseKey;
1771
    end;
1772
  finally
1773
    reg.Free;
1774
  end;
1775
end;
1776
 
1777
class function TRecycleBinManager.GetGlobalNukeOnDelete: boolean;
1778
var
1779
  reg: TRegistry;
1780
  purgeInfo: TRbWin95PurgeInfo;
1781
const
1782
  RES_DEFAULT = false; // Windows 95 - Standardwert
1783
begin
78 daniel-mar 1784
  if Win32MajorVersion >= 6 then
1785
  begin
1786
    // Only available till Windows XP
1787
    result := false;
1788
    exit;
1789
  end;
1790
 
75 daniel-mar 1791
  result := RES_DEFAULT;
1792
 
1793
  reg := TRegistry.Create;
1794
  try
1795
    reg.RootKey := HKEY_LOCAL_MACHINE;
1796
 
1797
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1798
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1799
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1800
    begin
1801
      if reg.ValueExists('NukeOnDelete') then
1802
      begin
1803
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1804
 
1805
        result := reg.ReadBool('NukeOnDelete');
1806
      end
1807
      else if reg.ValueExists('PurgeInfo') then
1808
      begin
1809
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1810
 
1811
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1812
        result := (purgeInfo.NukeOnDeleteBits and $8000000) = $8000000; // bit 27
1813
      end;
1814
 
1815
      reg.CloseKey;
1816
    end;
1817
  finally
1818
    reg.Free;
1819
  end;
1820
end;
1821
 
76 daniel-mar 1822
(* TODO:
1823
There are more registry values (found in WinXP):
1824
 
1825
BitBucket\<driveletter>
1826
  VolumeSerialNumber
1827
  IsUnicode
1828
 
1829
*)
1830
 
75 daniel-mar 1831
class function TRecycleBinManager.UsesGlobalSettings: boolean;
1832
var
1833
  reg: TRegistry;
1834
  purgeInfo: TRbWin95PurgeInfo;
1835
const
1836
  RES_DEFAULT = true; // Windows 95 - Standardwert
1837
begin
78 daniel-mar 1838
  if Win32MajorVersion >= 6 then
1839
  begin
1840
    // Only available till Windows XP
1841
    result := false;
1842
    exit;
1843
  end;
1844
 
75 daniel-mar 1845
  result := RES_DEFAULT;
1846
 
1847
  reg := TRegistry.Create;
1848
  try
1849
    reg.RootKey := HKEY_LOCAL_MACHINE;
1850
 
1851
    // Im Auslieferungszustand von Windows 95 ist dieser Schlüssel nicht vorhanden.
1852
    // Er wird bei der ersten Änderung der Papierkorb-Einstellungen erstellt.
1853
    if reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\BitBucket') then
1854
    begin
1855
      if reg.ValueExists('UseGlobalSettings') then
1856
      begin
1857
        // Windows 2000 - Informationen liegen aufgeschlüsselt in der Registry
1858
 
1859
        result := reg.ReadBool('UseGlobalSettings');
1860
      end
1861
      else if reg.ValueExists('PurgeInfo') then
1862
      begin
1863
        // Windows 95 - Kodierte Informationen liegen in PurgeInfo
1864
 
1865
        reg.ReadBinaryData('PurgeInfo', purgeInfo, SizeOf(purgeInfo));
1866
        result := purgeInfo.bGlobalSettings;
1867
      end;
1868
 
1869
      reg.CloseKey;
1870
    end;
1871
  finally
1872
    reg.Free;
1873
  end;
1874
end;
1875
 
1876
class procedure TRecycleBinManager.ListDrives(list: TObjectList{TRbDrive});
1877
var
89 daniel-mar 1878
  drive: AnsiChar;
75 daniel-mar 1879
begin
1880
  for drive := 'A' to 'Z' do
1881
    if RecycleBinPossible(drive) then
1882
      list.Add(TRbDrive.Create(drive));
1883
end;
1884
 
1885
class function TRecycleBinManager.OwnRecyclersEmpty: boolean;
1886
var
1887
  drives: TObjectList;
1888
  i: integer;
1889
begin
1890
  result := true;
1891
 
1892
  drives := TObjectList.Create(true);
1893
  try
1894
    ListDrives(drives);
1895
    for i := 0 to drives.Count - 1 do
1896
    begin
1897
      result := result and TRbDrive(drives.Items[i]).IsEmpty;
1898
      if not result then break;
1899
    end;
1900
  finally
1901
    drives.Free;
1902
  end;
1903
end;
1904
 
1905
class function TRecycleBinManager.OwnRecyclersNumItems: int64;
1906
var
1907
  drives: TObjectList;
1908
  i: integer;
1909
begin
1910
  result := 0;
1911
 
1912
  drives := TObjectList.Create(true);
1913
  try
1914
    ListDrives(drives);
1915
    for i := 0 to drives.Count - 1 do
1916
    begin
1917
      result := result + TRbDrive(drives.Items[i]).GetNumItems;
1918
    end;
1919
  finally
1920
    drives.Free;
1921
  end;
1922
end;
1923
 
1924
class function TRecycleBinManager.OwnRecyclersSize: int64;
1925
var
1926
  drives: TObjectList;
1927
  i: integer;
1928
begin
1929
  result := 0;
1930
 
1931
  drives := TObjectList.Create(true);
1932
  try
1933
    ListDrives(drives);
1934
    for i := 0 to drives.Count - 1 do
1935
    begin
1936
      result := result + TRbDrive(drives.Items[i]).GetSize;
1937
    end;
1938
  finally
1939
    drives.Free;
1940
  end;
1941
end;
1942
 
89 daniel-mar 1943
class function TRecycleBinManager.RecycleBinPossible(Drive: AnsiChar): boolean;
75 daniel-mar 1944
var
1945
  typ: Integer;
1946
begin
1947
  // Does the drive exist?
1948
  // see http://www.delphipraxis.net/post2933.html
1949
  result := GetLogicalDrives and (1 shl DriveLetterToDriveNumber(Drive)) <> 0;
1950
  if not result then exit;
1951
 
1952
  // Is it a fixed drive? (Only they can have recycle bins)
1953
  // TODO: is that correct, or can also have other drive types have recyclers?
1954
  typ := GetDriveType(PChar(Drive + ':\'));
1955
  result := typ = DRIVE_FIXED;
1956
end;
1957
 
1958
class function TRecycleBinManager.RecyclerGetCurrentIconString: string;
1959
begin
1960
  if OwnRecyclersEmpty then
1961
    result := RecyclerGetEmptyIconString
1962
  else
1963
    result := RecyclerGetFullIconString;
1964
end;
1965
 
1966
class function TRecycleBinManager.RecyclerGetDefaultIconString: string;
1967
var
1968
  reg: TRegistry;
1969
begin
1970
  // Please note: The "default" icon is not always the icon of the
1971
  // current recycle bin in its current state (full, empty)
1972
  // At Windows 95b, the registry value actually did change every time the
1973
  // recycle bin state did change, but at Windows 2000 I could not see any
1974
  // update, even after reboot. So, the registry value is possible fixed as
1975
  // default = empty on newer OS versions.
1976
 
1977
  reg := TRegistry.Create;
1978
  try
1979
    reg.RootKey := HKEY_CLASSES_ROOT;
1980
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
1981
    begin
1982
      result := reg.ReadString('');
1983
      reg.CloseKey;
1984
    end;
1985
  finally
1986
    reg.Free;
1987
  end;
1988
end;
1989
 
1990
class function TRecycleBinManager.RecyclerGetEmptyIconString: string;
1991
var
1992
  reg: TRegistry;
1993
begin
1994
  reg := TRegistry.Create;
1995
  try
1996
    reg.RootKey := HKEY_CLASSES_ROOT;
1997
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
1998
    begin
1999
      result := reg.ReadString('Empty');
2000
      reg.CloseKey;
2001
    end;
2002
  finally
2003
    reg.Free;
2004
  end;
2005
end;
2006
 
2007
class function TRecycleBinManager.RecyclerGetFullIconString: string;
2008
var
2009
  reg: TRegistry;
2010
begin
2011
  reg := TRegistry.Create;
2012
  try
2013
    reg.RootKey := HKEY_CLASSES_ROOT;
2014
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)+'\DefaultIcon') then
2015
    begin
2016
      result := reg.ReadString('Full');
2017
      reg.CloseKey;
2018
    end;
2019
  finally
2020
    reg.Free;
2021
  end;
2022
end;
2023
 
2024
class function TRecycleBinManager.RecyclerGetInfoTip: string;
2025
var
2026
  reg: TRegistry;
2027
begin
2028
  // Not available in some older versions of Windows
2029
 
2030
  reg := TRegistry.Create;
2031
  try
2032
    reg.RootKey := HKEY_CLASSES_ROOT;
2033
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
2034
    begin
2035
      result := reg.ReadString('InfoTip');
2036
      result := DecodeReferenceString(result);
2037
 
2038
      reg.CloseKey;
2039
    end;
2040
  finally
2041
    reg.Free;
2042
  end;
2043
end;
2044
 
2045
class function TRecycleBinManager.RecyclerGetIntroText: string;
2046
var
2047
  reg: TRegistry;
2048
begin
2049
  // Not available in some older versions of Windows
2050
 
2051
  reg := TRegistry.Create;
2052
  try
2053
    reg.RootKey := HKEY_CLASSES_ROOT;
2054
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
2055
    begin
2056
      result := reg.ReadString('IntroText');
2057
      result := DecodeReferenceString(result);
2058
 
2059
      reg.CloseKey;
2060
    end;
2061
  finally
2062
    reg.Free;
2063
  end;
2064
end;
2065
 
2066
class function TRecycleBinManager.RecyclerGetName: string;
2067
var
2068
  reg: TRegistry;
2069
begin
2070
  // Windows 95b:
2071
  // Change of CLSID\{645FF040-5081-101B-9F08-00AA002F954E} will change the desktop name of the recycle bin.
2072
 
2073
  // Windows 2000: If LocalizedString is available, the 3rd argument will be parsed
2074
  // (if the third argument will removed, it will be read out from the DLL resource string automatically)
2075
 
2076
  reg := TRegistry.Create;
2077
  try
2078
    reg.RootKey := HKEY_CLASSES_ROOT;
2079
    if reg.OpenKeyReadOnly('CLSID\'+GUIDToString(RECYCLER_CLSID)) then
2080
    begin
2081
      if reg.ValueExists('LocalizedString') then
2082
      begin
2083
        result := reg.ReadString('LocalizedString');
2084
        result := DecodeReferenceString(result);
2085
      end
2086
      else
2087
      begin
2088
        result := reg.ReadString('');
2089
      end;
2090
 
2091
      reg.CloseKey;
2092
    end;
2093
  finally
2094
    reg.Free;
2095
  end;
2096
end;
2097
 
2098
class function TRecycleBinManager.RecyclerEmptyEventGetName: string;
2099
var
2100
  reg: TRegistry;
2101
begin
2102
  reg := TRegistry.Create;
2103
  try
2104
    reg.RootKey := HKEY_CURRENT_USER;
2105
    if reg.OpenKeyReadOnly('AppEvents\EventLabels\EmptyRecycleBin') then
2106
    begin
2107
      result := reg.ReadString('');
2108
      reg.CloseKey;
2109
    end;
2110
  finally
2111
    reg.Free;
2112
  end;
2113
end;
2114
 
2115
class function TRecycleBinManager.RecyclerEmptyEventGetCurrentSound: string;
2116
begin
2117
  result := RecyclerEmptyEventGetSound('.Current');
2118
end;
2119
 
2120
class function TRecycleBinManager.RecyclerEmptyEventGetDefaultSound: string;
2121
begin
2122
  result := RecyclerEmptyEventGetSound('.Default');
2123
end;
2124
 
2125
class procedure TRecycleBinManager.RecyclerEmptyEventGetSoundCategories(AStringList: TStringList);
2126
var
2127
  reg: TRegistry;
2128
begin
2129
  reg := TRegistry.Create;
2130
  try
2131
    reg.RootKey := HKEY_CURRENT_USER;
2132
    if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
2133
    begin
2134
      reg.GetKeyNames(AStringList);
2135
      reg.CloseKey;
2136
    end;
2137
  finally
2138
    reg.Free;
2139
  end;
2140
end;
2141
 
2142
class function TRecycleBinManager.RecyclerEmptyEventGetSound(ACategory: string): string;
2143
var
2144
  reg: TRegistry;
2145
resourcestring
2146
  LNG_SND_EVENT_CAT_ERROR = 'The category "%s" is not available for the notification event "%s".';
2147
begin
2148
  // Outputs an filename or empty string for no sound defined.
2149
 
2150
  reg := TRegistry.Create;
2151
  try
2152
    reg.RootKey := HKEY_CURRENT_USER;
2153
    if reg.OpenKeyReadOnly('AppEvents\Schemes\Apps\Explorer\EmptyRecycleBin') then
2154
    begin
2155
      if reg.OpenKeyReadOnly(ACategory) then
2156
      begin
2157
        result := reg.ReadString('');
2158
        reg.CloseKey;
2159
      end
2160
      else
2161
        raise EEventCategoryNotDefined.CreateFmt(LNG_SND_EVENT_CAT_ERROR, [ACategory, 'EmptyRecycleBin']);
2162
      reg.CloseKey;
2163
    end;
2164
  finally
2165
    reg.Free;
2166
  end;
2167
end;
2168
 
2169
class function TRecycleBinManager.RecyclerQueryFunctionAvailable: boolean;
2170
var
2171
  RBHandle: THandle;
2172
  SHQueryRecycleBin: TSHQueryRecycleBin;
2173
begin
2174
  // Windows 95 without Internet Explorer 4 has no SHQueryRecycleBinA.
2175
  RBHandle := LoadLibrary(shell32);
2176
  try
2177
    if RBHandle <> 0 then
2178
    begin
2179
      SHQueryRecycleBin := GetProcAddress(RBHandle, C_SHQueryRecycleBin);
2180
      if not Assigned(@SHQueryRecycleBin) then
2181
      begin
2182
        FreeLibrary(RBHandle);
2183
        RBHandle := 0;
2184
      end;
2185
    end;
2186
 
2187
    result := RBHandle <> 0;
2188
  finally
2189
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2190
  end;
2191
end;
2192
 
2193
class function TRecycleBinManager.RecyclerAddFileOrFolder(FileOrFolder: string; confirmation: boolean=false): boolean;
2194
var
2195
  Operation: TSHFileOpStruct;
2196
begin
2197
  // Template: http://www.dsdt.info/tipps/?id=116
2198
  with Operation do
2199
  begin
2200
    Wnd := hInstance; // OK?
2201
    wFunc := FO_DELETE;
2202
    pFrom := PChar(FileOrFolder + #0);
2203
    pTo := nil;
2204
    fFlags := FOF_ALLOWUNDO;
2205
    if not confirmation then fFlags := fFlags or FOF_NOCONFIRMATION;
2206
  end;
2207
  Result := SHFileOperation(Operation) = 0;
2208
end;
2209
 
2210
class function TRecycleBinManager.RecyclerGroupPolicyNoRecycleFiles: GPOLICYBOOL;
2211
var
2212
  reg: TRegistry;
2213
begin
2214
  result := gpUndefined;
2215
 
2216
  reg := TRegistry.Create;
2217
  try
2218
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2219
    // even if gpedit.msc shows "Not configured"!
2220
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2221
    reg.RootKey := HKEY_LOCAL_MACHINE;
2222
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2223
    begin
2224
      if reg.ValueExists('NoRecycleFiles') then
2225
      begin
2226
        if reg.ReadBool('NoRecycleFiles') then
2227
          result := gpEnabled
2228
        else
2229
          result := gpDisabled;
2230
        Exit;
2231
      end;
2232
      reg.CloseKey;
2233
    end;
2234
    {$ENDIF}
2235
 
2236
    reg.RootKey := HKEY_CURRENT_USER;
2237
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2238
    begin
2239
      if reg.ValueExists('NoRecycleFiles') then
2240
      begin
2241
        if reg.ReadBool('NoRecycleFiles') then
2242
          result := gpEnabled
2243
        else
2244
          result := gpDisabled;
2245
      end;
2246
      reg.CloseKey;
2247
    end;
2248
  finally
2249
    reg.Free;
2250
  end;
2251
end;
2252
 
2253
class function TRecycleBinManager.RecyclerGroupPolicyConfirmFileDelete: GPOLICYBOOL;
2254
var
2255
  reg: TRegistry;
2256
begin
2257
  result := gpUndefined;
2258
  reg := TRegistry.Create;
2259
  try
2260
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2261
    // even if gpedit.msc shows "Not configured"!
2262
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2263
    reg.RootKey := HKEY_LOCAL_MACHINE;
2264
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2265
    begin
2266
      if reg.ValueExists('ConfirmFileDelete') then
2267
      begin
2268
        if reg.ReadBool('ConfirmFileDelete') then
2269
          result := gpEnabled
2270
        else
2271
          result := gpDisabled;
2272
        Exit;
2273
      end;
2274
      reg.CloseKey;
2275
    end;
2276
    {$ENDIF}
2277
 
2278
    reg.RootKey := HKEY_CURRENT_USER;
2279
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2280
    begin
2281
      if reg.ValueExists('ConfirmFileDelete') then
2282
      begin
2283
        if reg.ReadBool('ConfirmFileDelete') then
2284
          result := gpEnabled
2285
        else
2286
          result := gpDisabled;
2287
      end;
2288
      reg.CloseKey;
2289
    end;
2290
  finally
2291
    reg.Free;
2292
  end;
2293
end;
2294
 
2295
class function TRecycleBinManager.RecyclerGroupPolicyRecycleBinSize: integer;
2296
var
2297
  reg: TRegistry;
2298
begin
2299
  result := -1;
2300
  reg := TRegistry.Create;
2301
  try
2302
    // If a value is set in HKEY_LOCAL_MACHINE, it will be prefered,
2303
    // even if gpedit.msc shows "Not configured"!
2304
    {$IFDEF GroupPolicyAcceptHKLMTrick}
2305
    reg.RootKey := HKEY_LOCAL_MACHINE;
2306
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2307
    begin
2308
      if reg.ValueExists('RecycleBinSize') then
2309
      begin
2310
        result := reg.ReadInteger('RecycleBinSize');
2311
        Exit;
2312
      end;
2313
      reg.CloseKey;
2314
    end;
2315
    {$ENDIF}
2316
 
2317
    reg.RootKey := HKEY_CURRENT_USER;
2318
    if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Policies\Explorer') then
2319
    begin
2320
      if reg.ValueExists('RecycleBinSize') then
2321
      begin
2322
        result := reg.ReadInteger('RecycleBinSize');
2323
      end;
2324
      reg.CloseKey;
2325
    end;
2326
  finally
2327
    reg.Free;
2328
  end;
2329
end;
2330
 
2331
class function TRecycleBinManager.RecyclerConfirmationDialogEnabled: boolean;
2332
var
2333
  gp: GPOLICYBOOL;
2334
begin
2335
  gp := RecyclerGroupPolicyConfirmFileDelete;
2336
  if gp <> gpUndefined then
2337
  begin
2338
    result := gp = gpEnabled;
2339
  end
2340
  else
2341
  begin
2342
    result := RecyclerShellStateConfirmationDialogEnabled;
2343
  end;
2344
end;
2345
 
2346
class function TRecycleBinManager.RecyclerShellStateConfirmationDialogEnabled: boolean;
2347
var
2348
  lpss: SHELLSTATE;
2349
  bNoConfirmRecycle: boolean;
2350
 
2351
  PSHGetSettings: TSHGetSettings;
2352
  RBHandle: THandle;
2353
 
2354
  reg: TRegistry;
2355
  rbuf: array[0..255] of byte;
2356
begin
2357
  PSHGetSettings := nil;
2358
  result := false; // Avoid warning message
2359
 
2360
  RBHandle := LoadLibrary(shell32);
2361
  try
2362
    if RBHandle <> 0 then
2363
    begin
2364
      PSHGetSettings := GetProcAddress(RBHandle, C_SHGetSettings);
2365
      if not Assigned(@PSHGetSettings) then
2366
      begin
2367
        FreeLibrary(RBHandle);
2368
        RBHandle := 0;
2369
      end;
2370
    end;
2371
 
2372
    if (RBHandle <> 0) and Assigned(PSHGetSettings) then
2373
    begin
2374
      ZeroMemory(@lpss, SizeOf(lpss));
2375
      PSHGetSettings(lpss, SSF_NOCONFIRMRECYCLE);
2376
      bNoConfirmRecycle := (lpss.Flags1 and 4) = 4; // fNoConfirmRecycle
2377
 
2378
      result := not bNoConfirmRecycle;
2379
    end
2380
    else
2381
    begin
2382
      reg := TRegistry.Create;
2383
      try
2384
        // API function call failed. Probably because Windows is too old.
2385
        // Try to read out from registry.
2386
        // The 3rd bit of the 5th byte of "ShellState" is the value
2387
        // of "fNoConfirmRecycle".
2388
 
2389
        reg.RootKey := HKEY_CURRENT_USER;
2390
        if reg.OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion\Explorer') then
2391
        begin
2392
          ZeroMemory(@rbuf, SizeOf(rbuf));
2393
          reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf));
2394
 
2395
          // Lese 3tes Bit vom 5ten Byte
2396
          bNoConfirmRecycle := ((rbuf[4] and 4) = 4);
2397
          result := not bNoConfirmRecycle;
2398
 
2399
          reg.CloseKey;
2400
        end
2401
        else
2402
        begin
2403
          raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSettings])]);
2404
        end;
2405
      finally
2406
        reg.Free;
2407
      end;
2408
    end;
2409
  finally
2410
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2411
  end;
2412
end;
2413
 
2414
class procedure TRecycleBinManager.RecyclerConfirmationDialogSetEnabled(NewSetting: boolean);
2415
var
2416
  lpss: SHELLSTATE;
2417
 
2418
  PSHGetSetSettings: TSHGetSetSettings;
2419
  RBHandle: THandle;
2420
 
2421
  reg: TRegistry;
2422
  rbuf: array[0..255] of byte;
2423
 
89 daniel-mar 2424
  //dwResult: DWORD;
2425
  lpdwResult: PDWORD_PTR;
75 daniel-mar 2426
begin
2427
  PSHGetSetSettings := nil;
89 daniel-mar 2428
  lpdwResult := nil;
75 daniel-mar 2429
 
2430
  RBHandle := LoadLibrary(shell32);
2431
  try
2432
    if RBHandle <> 0 then
2433
    begin
2434
      PSHGetSetSettings := GetProcAddress(RBHandle, C_SHGetSetSettings);
2435
      if not Assigned(@PSHGetSetSettings) then
2436
      begin
2437
        FreeLibrary(RBHandle);
2438
        RBHandle := 0;
2439
      end;
2440
    end;
2441
 
2442
    if (RBHandle <> 0) and Assigned(PSHGetSetSettings) then
2443
    begin
2444
      ZeroMemory(@lpss, SizeOf(lpss));
2445
 
2446
      PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, false); // Get
2447
 
2448
      // Set 3rd bit equal to NewSetting
2449
      if NewSetting then
2450
        lpss.Flags1 := lpss.Flags1 or  $00000004
2451
      else
2452
        lpss.Flags1 := lpss.Flags1 and $FFFFFFFB;
2453
 
2454
      PSHGetSetSettings(lpss, SSF_NOCONFIRMRECYCLE, true); // Set
2455
 
2456
      SendMessageTimeout (
2457
        HWND_BROADCAST, WM_SETTINGCHANGE,
2458
        0, lParam (pChar ('ShellState')),
89 daniel-mar 2459
        SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
75 daniel-mar 2460
      );
2461
    end
2462
    else
2463
    begin
2464
      reg := TRegistry.Create;
2465
      try
2466
        // API function call failed. Probably because Windows is too old.
2467
        // Try to read out from registry.
2468
        // The 3rd bit of the 5th byte of "ShellState" is the value
2469
        // of "fNoConfirmRecycle".
2470
 
2471
        reg.RootKey := HKEY_CURRENT_USER;
2472
        if reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer', false) then
2473
        begin
2474
          ZeroMemory(@rbuf, SizeOf(rbuf));
2475
 
2476
          reg.ReadBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Get
2477
 
2478
          // Set 3rd bit equal to NewSetting
2479
          if NewSetting then
2480
            rbuf[4] := rbuf[4] or  $04
2481
          else
2482
            rbuf[4] := rbuf[4] and $FB;
2483
 
2484
          reg.WriteBinaryData('ShellState', rbuf, SizeOf(rbuf)); // Set
2485
 
2486
          SendMessageTimeout (
2487
            HWND_BROADCAST, WM_SETTINGCHANGE,
2488
            0, lParam (pChar ('ShellState')),
89 daniel-mar 2489
            SMTO_ABORTIFHUNG, 5000, lpdwResult(*dwResult*)
75 daniel-mar 2490
          );
2491
 
2492
          reg.CloseKey;
2493
        end
2494
        else
2495
        begin
2496
          raise EAPICallError.CreateFmt(LNG_API_CALL_ERROR, [Format(LNG_NOT_CALLABLE, [C_SHGetSetSettings])]);
2497
        end;
2498
      finally
2499
        reg.Free;
2500
      end;
2501
    end;
2502
  finally
2503
    if RBHandle <> 0 then FreeLibrary(RBHandle);
2504
  end;
2505
end;
2506
 
2507
{ TRbRecycleBinItem }
2508
 
2509
function TRbRecycleBinItem.GetSource: string;
2510
begin
2511
  {$IFDEF UNICODE}
2512
  result := SourceUnicode;
2513
  {$ELSE}
2514
  result := SourceAnsi;
2515
  {$ENDIF}
2516
end;
2517
 
2518
end.