Subversion Repositories recyclebinunit

Rev

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