mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-28 10:02:14 +00:00
1277 lines
36 KiB
ObjectPascal
1277 lines
36 KiB
ObjectPascal
{
|
|
Double Commander
|
|
-------------------------------------------------------------------------
|
|
Directories synchronization utility (specially for DC)
|
|
|
|
Copyright (C) 2013 Anton Panferov (ast.a_s@mail.ru)
|
|
Copyright (C) 2014-2016 Alexander Koblov (alexx2000@mail.ru)
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
}
|
|
|
|
unit fSyncDirsDlg;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FileUtil, Masks, Forms, Controls, Graphics, Dialogs,
|
|
StdCtrls, ExtCtrls, Buttons, ComCtrls, Grids, Menus, uFileView, uFileSource,
|
|
uFileSourceCopyOperation, uFile, uFileSourceOperationMessageBoxesUI;
|
|
|
|
type
|
|
|
|
TSyncRecState = (srsUnknown, srsEqual, srsNotEq, srsCopyLeft, srsCopyRight, srsDeleteRight,
|
|
srsDoNothing);
|
|
|
|
{ TfrmSyncDirsDlg }
|
|
|
|
TfrmSyncDirsDlg = class(TForm)
|
|
btnSelDir1: TButton;
|
|
btnSelDir2: TButton;
|
|
btnCompare: TButton;
|
|
btnSynchronize: TButton;
|
|
btnClose: TButton;
|
|
chkAsymmetric: TCheckBox;
|
|
chkSubDirs: TCheckBox;
|
|
chkByContent: TCheckBox;
|
|
chkIgnoreDate: TCheckBox;
|
|
chkOnlySelected: TCheckBox;
|
|
cbExtFilter: TComboBox;
|
|
HeaderDG: TDrawGrid;
|
|
MainDrawGrid: TDrawGrid;
|
|
edPath1: TEdit;
|
|
edPath2: TEdit;
|
|
GroupBox1: TGroupBox;
|
|
ImageList1: TImageList;
|
|
Label1: TLabel;
|
|
LeftPanel1: TPanel;
|
|
LeftPanel2: TPanel;
|
|
MenuItemCompare: TMenuItem;
|
|
MenuItemViewRight: TMenuItem;
|
|
MenuItemViewLeft: TMenuItem;
|
|
pmGridMenu: TPopupMenu;
|
|
sbCopyRight: TSpeedButton;
|
|
sbEqual: TSpeedButton;
|
|
sbNotEqual: TSpeedButton;
|
|
sbCopyLeft: TSpeedButton;
|
|
sbDuplicates: TSpeedButton;
|
|
sbSingles: TSpeedButton;
|
|
StatusBar1: TStatusBar;
|
|
TopPanel: TPanel;
|
|
procedure btnCloseClick(Sender: TObject);
|
|
procedure btnSelDir1Click(Sender: TObject);
|
|
procedure btnCompareClick(Sender: TObject);
|
|
procedure btnSynchronizeClick(Sender: TObject);
|
|
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
|
|
procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure MainDrawGridDblClick(Sender: TObject);
|
|
procedure MainDrawGridDrawCell(Sender: TObject; aCol, aRow: Integer;
|
|
aRect: TRect; aState: TGridDrawState);
|
|
procedure MainDrawGridMouseDown(Sender: TObject; Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure HeaderDGHeaderClick(Sender: TObject; IsColumn: Boolean;
|
|
Index: Integer);
|
|
procedure HeaderDGHeaderSizing(sender: TObject; const IsColumn: boolean;
|
|
const aIndex, aSize: Integer);
|
|
procedure FilterSpeedButtonClick(Sender: TObject);
|
|
procedure MenuItemViewClick(Sender: TObject);
|
|
private
|
|
{ private declarations }
|
|
FCancel: Boolean;
|
|
FFoundItems: TStringList;
|
|
FVisibleItems: TStringList;
|
|
FSortIndex: Integer;
|
|
FSortDesc: Boolean;
|
|
FNtfsShift: Boolean;
|
|
FFileExists: TSyncRecState;
|
|
FFileSourceL, FFileSourceR: IFileSource;
|
|
FCmpFileSourceL, FCmpFileSourceR: IFileSource;
|
|
FCmpFilePathL, FCmpFilePathR: string;
|
|
FAddressL, FAddressR: string;
|
|
hCols: array [0..6] of record Left, Width: Integer end;
|
|
CheckContentThread: TObject;
|
|
Ftotal, Fequal, Fnoneq, FuniqueL, FuniqueR: Integer;
|
|
FFileSourceOperationMessageBoxesUI: TFileSourceOperationMessageBoxesUI;
|
|
procedure ClearFoundItems;
|
|
procedure Compare;
|
|
procedure FillFoundItemsDG;
|
|
procedure InitVisibleItems;
|
|
procedure RecalcHeaderCols;
|
|
procedure ScanDirs;
|
|
procedure SetSortIndex(AValue: Integer);
|
|
procedure SortFoundItems;
|
|
procedure SortFoundItems(sl: TStringList);
|
|
procedure UpdateStatusBar;
|
|
procedure StopCheckContentThread;
|
|
property SortIndex: Integer read FSortIndex write SetSortIndex;
|
|
public
|
|
{ public declarations }
|
|
constructor Create(AOwner: TComponent;
|
|
FileView1, FileView2: TFileView); reintroduce;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
resourcestring
|
|
rsComparingPercent = 'Comparing... %d%% (ESC to cancel)';
|
|
rsLeftToRightCopy = 'Left to Right: Copy %d files, total size: %d bytes';
|
|
rsRightToLeftCopy = 'Right to Left: Copy %d files, total size: %d bytes';
|
|
rsDeleteRight = 'Right: Delete %d file(s)';
|
|
rsFilesFound = 'Files found: %d (Identical: %d, Different: %d, '
|
|
+ 'Unique left: %d, Unique right: %d)';
|
|
|
|
procedure ShowSyncDirsDlg(FileView1, FileView2: TFileView);
|
|
|
|
implementation
|
|
|
|
uses
|
|
fMain, uDebug, fDiffer, fSyncDirsPerformDlg, uGlobs, LCLType, LazUTF8, LazFileUtils,
|
|
DCClassesUtf8, uFileSystemFileSource, uFileSourceOperationOptions, DCDateTimeUtils,
|
|
uFileSourceOperation, uDCUtils, uFileSourceUtil, uFileSourceOperationTypes,
|
|
uShowForm, uFileSourceDeleteOperation, uOSUtils, uLng;
|
|
|
|
{$R *.lfm}
|
|
|
|
type
|
|
|
|
{ TFileSyncRec }
|
|
|
|
TFileSyncRec = class
|
|
private
|
|
FRelPath: string;
|
|
FState: TSyncRecState;
|
|
FAction: TSyncRecState;
|
|
FFileR, FFileL: TFile;
|
|
FForm: TfrmSyncDirsDlg;
|
|
public
|
|
constructor Create(AForm: TfrmSyncDirsDlg; RelPath: string);
|
|
destructor Destroy; override;
|
|
procedure UpdateState(ignoreDate: Boolean);
|
|
end;
|
|
|
|
{ TCheckContentThread }
|
|
|
|
TCheckContentThread = class(TThread)
|
|
private
|
|
FOwner: TfrmSyncDirsDlg;
|
|
FDone: Boolean;
|
|
procedure UpdateGrid;
|
|
procedure ReapplyFilter;
|
|
procedure DoOnTerminate(Sender: TObject);
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create(Owner: TfrmSyncDirsDlg);
|
|
property Done: Boolean read FDone;
|
|
end;
|
|
|
|
procedure ShowSyncDirsDlg(FileView1, FileView2: TFileView);
|
|
begin
|
|
if not Assigned(FileView1) then
|
|
raise Exception.Create('ShowSyncDirsDlg: FileView1=nil');
|
|
if not Assigned(FileView2) then
|
|
raise Exception.Create('ShowSyncDirsDlg: FileView2=nil');
|
|
with TfrmSyncDirsDlg.Create(Application, FileView1, FileView2) do
|
|
Show;
|
|
end;
|
|
|
|
{ TCheckContentThread }
|
|
|
|
procedure TCheckContentThread.DoOnTerminate(Sender: TObject);
|
|
begin
|
|
FOwner.CheckContentThread := nil;
|
|
FOwner := nil;
|
|
end;
|
|
|
|
procedure TCheckContentThread.UpdateGrid;
|
|
begin
|
|
FOwner.MainDrawGrid.Invalidate;
|
|
FOwner.UpdateStatusBar;
|
|
end;
|
|
|
|
procedure TCheckContentThread.ReapplyFilter;
|
|
begin
|
|
FOwner.FillFoundItemsDG;
|
|
FOwner.UpdateStatusBar;
|
|
end;
|
|
|
|
procedure TCheckContentThread.Execute;
|
|
|
|
function CompareFiles(fn1, fn2: String; len: Int64): Boolean;
|
|
const
|
|
BUFLEN = 1024 * 32;
|
|
var
|
|
fs1, fs2: TFileStreamEx;
|
|
buf1, buf2: array [1..BUFLEN] of Byte;
|
|
i, j: Int64;
|
|
begin
|
|
fs1 := TFileStreamEx.Create(fn1, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
fs2 := TFileStreamEx.Create(fn2, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
i := 0;
|
|
repeat
|
|
if len - i <= BUFLEN then
|
|
j := len - i
|
|
else
|
|
j := BUFLEN;
|
|
fs1.Read(buf1, j);
|
|
fs2.Read(buf2, j);
|
|
i := i + j;
|
|
Result := CompareMem(@buf1, @buf2, j);
|
|
until Terminated or not Result or (i >= len);
|
|
finally
|
|
fs2.Free;
|
|
end;
|
|
finally
|
|
fs1.Free;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i, j: Integer;
|
|
r: TFileSyncRec;
|
|
begin
|
|
with FOwner do
|
|
for i := 0 to FFoundItems.Count - 1 do
|
|
for j := 0 to TStringList(FFoundItems.Objects[i]).Count - 1 do
|
|
begin
|
|
if Terminated then Exit;
|
|
r := TFileSyncRec(TStringList(FFoundItems.Objects[i]).Objects[j]);
|
|
if Assigned(r) and (r.FState = srsUnknown) then
|
|
begin
|
|
try
|
|
if CompareFiles(r.FFileL.FullPath, r.FFileR.FullPath, r.FFileL.Size) then
|
|
begin
|
|
Inc(Fequal);
|
|
r.FState := srsEqual
|
|
end
|
|
else
|
|
r.FState := srsNotEq;
|
|
if r.FAction = srsUnknown then
|
|
r.FAction := r.FState;
|
|
if j mod 20 = 0 then
|
|
Synchronize(@UpdateGrid);
|
|
except
|
|
on e: Exception do
|
|
DCDebug('[SyncDirs::CmpContentThread] ' + e.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
FDone := True;
|
|
Synchronize(@ReapplyFilter);
|
|
end;
|
|
|
|
constructor TCheckContentThread.Create(Owner: TfrmSyncDirsDlg);
|
|
begin
|
|
inherited Create(True);
|
|
OnTerminate := @DoOnTerminate;
|
|
FreeOnTerminate := True;
|
|
FOwner := Owner;
|
|
Start;
|
|
end;
|
|
|
|
constructor TFileSyncRec.Create(AForm: TfrmSyncDirsDlg; RelPath: string);
|
|
begin
|
|
FForm:= AForm;
|
|
FRelPath := RelPath;
|
|
end;
|
|
|
|
destructor TFileSyncRec.Destroy;
|
|
begin
|
|
FreeAndNil(FFileL);
|
|
FreeAndNil(FFileR);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TFileSyncRec.UpdateState(ignoreDate: Boolean);
|
|
var
|
|
FileTimeDiff: Integer;
|
|
begin
|
|
FState := srsNotEq;
|
|
if Assigned(FFileR) and not Assigned(FFileL) then
|
|
FState := FForm.FFileExists
|
|
else
|
|
if not Assigned(FFileR) and Assigned(FFileL) then
|
|
FState := srsCopyRight
|
|
else begin
|
|
FileTimeDiff := FileTimeCompare(FFileL.ModificationTime, FFileR.ModificationTime, FForm.FNtfsShift);
|
|
if ((FileTimeDiff = 0) or ignoreDate) and (FFileL.Size = FFileR.Size) then
|
|
FState := srsEqual
|
|
else
|
|
if not ignoreDate then
|
|
if FileTimeDiff > 0 then
|
|
FState := srsCopyRight
|
|
else
|
|
if FileTimeDiff < 0 then
|
|
FState := srsCopyLeft;
|
|
end;
|
|
FAction := FState;
|
|
end;
|
|
|
|
{ TfrmSyncDirsDlg }
|
|
|
|
procedure TfrmSyncDirsDlg.btnCloseClick(Sender: TObject);
|
|
begin
|
|
Close
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.btnSelDir1Click(Sender: TObject);
|
|
var w: TEdit;
|
|
begin
|
|
w := nil;
|
|
case TComponent(Sender).Tag of
|
|
0: w := edPath1;
|
|
1: w := edPath2;
|
|
end;
|
|
if w = nil then Exit;
|
|
with TSelectDirectoryDialog.Create(Self) do
|
|
try
|
|
InitialDir := w.Text;
|
|
if Execute then
|
|
begin
|
|
w.Text := FileName;
|
|
case TComponent(Sender).Tag of
|
|
0:
|
|
begin
|
|
FFileSourceL := TFileSystemFileSource.GetFileSource;
|
|
FAddressL := '';
|
|
end;
|
|
1:
|
|
begin
|
|
FFileSourceR := TFileSystemFileSource.GetFileSource;
|
|
FAddressR := '';
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
Free
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.btnCompareClick(Sender: TObject);
|
|
begin
|
|
InsertFirstItem(Trim(cbExtFilter.Text), cbExtFilter);
|
|
StatusBar1.Panels[0].Text := Format(rsComparingPercent, [0]);
|
|
StopCheckContentThread;
|
|
Compare;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.btnSynchronizeClick(Sender: TObject);
|
|
var
|
|
OperationType: TFileSourceOperationType;
|
|
FileExistsOption: TFileSourceOperationOptionFileExists;
|
|
|
|
function CopyFiles(src, dst: IFileSource; fs: TFiles; Dest: string): Boolean;
|
|
var
|
|
Operation: TFileSourceCopyOperation = nil;
|
|
begin
|
|
if not GetCopyOperationType(Src, Dst, OperationType) then
|
|
begin
|
|
MessageDlg(rsMsgErrNotSupported, mtError, [mbOK], 0);
|
|
Exit(False);
|
|
end
|
|
else begin
|
|
Fs.Path:= fs[0].Path;
|
|
// Create destination directory
|
|
Dst.CreateDirectory(Dest);
|
|
// Determine operation type
|
|
case OperationType of
|
|
fsoCopy:
|
|
begin
|
|
// Copy within the same file source.
|
|
Operation := Src.CreateCopyOperation(
|
|
Fs,
|
|
Dest) as TFileSourceCopyOperation;
|
|
end;
|
|
fsoCopyOut:
|
|
begin
|
|
// CopyOut to filesystem.
|
|
Operation := Src.CreateCopyOutOperation(
|
|
Dst,
|
|
Fs,
|
|
Dest) as TFileSourceCopyOperation;
|
|
end;
|
|
fsoCopyIn:
|
|
begin
|
|
// CopyIn from filesystem.
|
|
Operation := Dst.CreateCopyInOperation(
|
|
Src,
|
|
Fs,
|
|
Dest) as TFileSourceCopyOperation;
|
|
end;
|
|
end;
|
|
if not Assigned(Operation) then
|
|
begin
|
|
MessageDlg(rsMsgErrNotSupported, mtError, [mbOK], 0);
|
|
Exit(False);
|
|
end;
|
|
Operation.FileExistsOption := FileExistsOption;
|
|
Operation.AddUserInterface(FFileSourceOperationMessageBoxesUI);
|
|
try
|
|
Operation.Execute;
|
|
Result := Operation.Result = fsorFinished;
|
|
FileExistsOption := Operation.FileExistsOption;
|
|
finally
|
|
Operation.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function DeleteFiles(FileSource: IFileSource; Files: TFiles): Boolean;
|
|
var
|
|
Operation: TFileSourceDeleteOperation;
|
|
begin
|
|
Files.Path := Files[0].Path;
|
|
Operation:= FileSource.CreateDeleteOperation(Files) as TFileSourceDeleteOperation;
|
|
if not Assigned(Operation) then
|
|
begin
|
|
MessageDlg(rsMsgErrNotSupported, mtError, [mbOK], 0);
|
|
Exit(False);
|
|
end;
|
|
Operation.AddUserInterface(FFileSourceOperationMessageBoxesUI);
|
|
try
|
|
Operation.Execute;
|
|
Result := Operation.Result = fsorFinished;
|
|
finally
|
|
Operation.Free;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i,
|
|
DeleteRightCount,
|
|
CopyLeftCount, CopyRightCount: Integer;
|
|
CopyLeftSize, CopyRightSize: Int64;
|
|
fsr: TFileSyncRec;
|
|
DeleteRight,
|
|
CopyLeft, CopyRight: Boolean;
|
|
DeleteRightFiles,
|
|
CopyLeftFiles, CopyRightFiles: TFiles;
|
|
Dest: string;
|
|
begin
|
|
DeleteRightCount := 0;
|
|
CopyLeftCount := 0; CopyRightCount := 0;
|
|
CopyLeftSize := 0; CopyRightSize := 0;
|
|
for i := 0 to FVisibleItems.Count - 1 do
|
|
if Assigned(FVisibleItems.Objects[i]) then
|
|
begin
|
|
fsr := TFileSyncRec(FVisibleItems.Objects[i]);
|
|
case fsr.FAction of
|
|
srsCopyLeft:
|
|
begin
|
|
Inc(CopyLeftCount);
|
|
Inc(CopyLeftSize, fsr.FFileR.Size);
|
|
end;
|
|
srsCopyRight:
|
|
begin
|
|
Inc(CopyRightCount);
|
|
Inc(CopyRightSize, fsr.FFileL.Size);
|
|
end;
|
|
srsDeleteRight:
|
|
begin
|
|
Inc(DeleteRightCount);
|
|
end;
|
|
end;
|
|
end;
|
|
with TfrmSyncDirsPerformDlg.Create(Self) do
|
|
try
|
|
edLeftPath.Text := FCmpFileSourceL.CurrentAddress + FCmpFilePathL;
|
|
edRightPath.Text := FCmpFileSourceR.CurrentAddress + FCmpFilePathR;
|
|
if (CopyLeftCount > 0) and
|
|
GetCopyOperationType(FFileSourceR, FFileSourceL, OperationType) then
|
|
begin
|
|
chkRightToLeft.Enabled := True;
|
|
chkRightToLeft.Checked := True;
|
|
edLeftPath.Enabled := True;
|
|
end;
|
|
if (CopyRightCount > 0) and
|
|
GetCopyOperationType(FFileSourceL, FFileSourceR, OperationType) then
|
|
begin
|
|
chkLeftToRight.Enabled := True;
|
|
chkLeftToRight.Checked := True;
|
|
edRightPath.Enabled := True;
|
|
end;
|
|
chkDeleteRight.Enabled := DeleteRightCount > 0;
|
|
chkDeleteRight.Checked := chkDeleteRight.Enabled;
|
|
chkDeleteRight.Caption := Format(rsDeleteRight, [DeleteRightCount]);
|
|
chkLeftToRight.Caption :=
|
|
Format(rsLeftToRightCopy, [CopyRightCount, CopyRightSize]);
|
|
chkRightToLeft.Caption :=
|
|
Format(rsRightToLeftCopy, [CopyLeftCount, CopyLeftSize]);
|
|
if ShowModal = mrOk then
|
|
begin
|
|
if chkConfirmOverwrites.Checked then
|
|
FileExistsOption := fsoofeNone
|
|
else begin
|
|
FileExistsOption := fsoofeOverwrite;
|
|
end;
|
|
CopyLeft := chkRightToLeft.Checked;
|
|
CopyRight := chkLeftToRight.Checked;
|
|
DeleteRight := chkDeleteRight.Checked;
|
|
i := 0;
|
|
while i < FVisibleItems.Count do
|
|
begin
|
|
CopyLeftFiles := TFiles.Create('');
|
|
CopyRightFiles := TFiles.Create('');
|
|
DeleteRightFiles := TFiles.Create('');
|
|
if FVisibleItems.Objects[i] <> nil then
|
|
repeat
|
|
fsr := TFileSyncRec(FVisibleItems.Objects[i]);
|
|
Dest := fsr.FRelPath;
|
|
case fsr.FAction of
|
|
srsCopyRight:
|
|
if CopyRight then CopyRightFiles.Add(fsr.FFileL.Clone);
|
|
srsCopyLeft:
|
|
if CopyLeft then CopyLeftFiles.Add(fsr.FFileR.Clone);
|
|
srsDeleteRight:
|
|
if DeleteRight then DeleteRightFiles.Add(fsr.FFileR.Clone);
|
|
end;
|
|
i := i + 1;
|
|
until (i = FVisibleItems.Count) or (FVisibleItems.Objects[i] = nil);
|
|
i := i + 1;
|
|
if CopyLeftFiles.Count > 0 then
|
|
begin
|
|
if not CopyFiles(FCmpFileSourceR, FCmpFileSourceL, CopyLeftFiles,
|
|
FCmpFilePathL + Dest) then Break;
|
|
end else CopyLeftFiles.Free;
|
|
if CopyRightFiles.Count > 0 then
|
|
begin
|
|
if not CopyFiles(FCmpFileSourceL, FCmpFileSourceR, CopyRightFiles,
|
|
FCmpFilePathR + Dest) then Break;
|
|
end else CopyRightFiles.Free;
|
|
if DeleteRightFiles.Count > 0 then
|
|
begin
|
|
if not DeleteFiles(FCmpFileSourceR, DeleteRightFiles) then Break;
|
|
end
|
|
else DeleteRightFiles.Free;
|
|
end;
|
|
btnCompare.Click;
|
|
end;
|
|
finally
|
|
Free
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FormClose(Sender: TObject;
|
|
var CloseAction: TCloseAction);
|
|
begin
|
|
CloseAction := caFree;
|
|
{ settings }
|
|
gSyncDirsSubdirs := chkSubDirs.Checked;
|
|
gSyncDirsIgnoreDate := chkIgnoreDate.Checked;
|
|
gSyncDirsShowFilterCopyRight := sbCopyRight.Down;
|
|
gSyncDirsShowFilterEqual := sbEqual.Down;
|
|
gSyncDirsShowFilterNotEqual := sbNotEqual.Down;
|
|
gSyncDirsShowFilterCopyLeft := sbCopyLeft.Down;
|
|
gSyncDirsShowFilterDuplicates := sbDuplicates.Down;
|
|
gSyncDirsShowFilterSingles := sbSingles.Down;
|
|
gSyncDirsFileMask := cbExtFilter.Text;
|
|
if chkByContent.Enabled then
|
|
gSyncDirsByContent := chkByContent.Checked;
|
|
glsMaskHistory.Assign(cbExtFilter.Items);
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FormCloseQuery(Sender: TObject; var CanClose: boolean);
|
|
begin
|
|
CanClose := FCancel;
|
|
if not FCancel then
|
|
begin
|
|
FCancel := True;
|
|
StopCheckContentThread;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FormCreate(Sender: TObject);
|
|
begin
|
|
// Initialize property storage
|
|
InitPropStorage(Self);
|
|
{ settings }
|
|
chkByContent.Enabled := FFileSourceL.IsClass(TFileSystemFileSource) and
|
|
FFileSourceR.IsClass(TFileSystemFileSource);
|
|
chkSubDirs.Checked := gSyncDirsSubdirs;
|
|
chkByContent.Checked := gSyncDirsByContent and chkByContent.Enabled;
|
|
chkIgnoreDate.Checked := gSyncDirsIgnoreDate;
|
|
sbCopyRight.Down := gSyncDirsShowFilterCopyRight;
|
|
sbEqual.Down := gSyncDirsShowFilterEqual;
|
|
sbNotEqual.Down := gSyncDirsShowFilterNotEqual;
|
|
sbCopyLeft.Down := gSyncDirsShowFilterCopyLeft;
|
|
sbDuplicates.Down := gSyncDirsShowFilterDuplicates;
|
|
sbSingles.Down := gSyncDirsShowFilterSingles;
|
|
cbExtFilter.Items.Assign(glsMaskHistory);
|
|
cbExtFilter.Text := gSyncDirsFileMask;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.MainDrawGridDblClick(Sender: TObject);
|
|
var
|
|
r, x: Integer;
|
|
sr: TFileSyncRec;
|
|
begin
|
|
r := MainDrawGrid.Row;
|
|
if (r < 0) or (r >= FVisibleItems.Count) then Exit;
|
|
x := MainDrawGrid.ScreenToClient(Mouse.CursorPos).X;
|
|
if (x > hCols[3].Left) and (x < hCols[3].Left + hCols[3].Width) then Exit;
|
|
sr := TFileSyncRec(FVisibleItems.Objects[r]);
|
|
if not Assigned(sr)
|
|
or not Assigned(sr.FFileR) or not Assigned(sr.FFileL) or (sr.FState = srsEqual)
|
|
then
|
|
Exit;
|
|
PrepareToolData(FFileSourceL, sr.FFileL, FFileSourceR, sr.FFileR, @ShowDifferByGlobList);
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.MainDrawGridDrawCell(Sender: TObject; aCol,
|
|
aRow: Integer; aRect: TRect; aState: TGridDrawState);
|
|
var
|
|
r: TFileSyncRec;
|
|
x: Integer;
|
|
s: string;
|
|
begin
|
|
if (FVisibleItems = nil) or (aRow >= FVisibleItems.Count) then Exit;
|
|
with MainDrawGrid.Canvas do
|
|
begin
|
|
r := TFileSyncRec(FVisibleItems.Objects[aRow]);
|
|
if r = nil then
|
|
begin
|
|
Brush.Color := clBtnFace;
|
|
FillRect(aRect);
|
|
Font.Bold := True;
|
|
Font.Color := clWindowText;
|
|
TextOut(aRect.Left + 2, aRect.Top + 2, FVisibleItems[aRow]);
|
|
end else begin
|
|
case r.FState of
|
|
srsNotEq: Font.Color := clRed;
|
|
srsCopyLeft: Font.Color := clBlue;
|
|
srsCopyRight: Font.Color := clGreen;
|
|
srsDeleteRight: Font.Color := clBlue;
|
|
else Font.Color := clWindowText;
|
|
end;
|
|
if Assigned(r.FFileL) then
|
|
begin
|
|
TextOut(aRect.Left + 2, aRect.Top + 2, FVisibleItems[aRow]);
|
|
s := IntToStr(r.FFileL.Size);
|
|
x := hCols[1].Left + hCols[1].Width - 2 - TextWidth(s);
|
|
TextOut(x, aRect.Top + 2, s);
|
|
s := DateTimeToStr(r.FFileL.ModificationTime);
|
|
with hCols[2] do
|
|
TextRect(Rect(Left, aRect.Top, Left + Width, aRect.Bottom),
|
|
Left + 2, aRect.Top + 2, s)
|
|
end;
|
|
if Assigned(r.FFileR) then
|
|
begin
|
|
TextOut(hCols[6].Left + 2, aRect.Top + 2, FVisibleItems[aRow]);
|
|
s := IntToStr(r.FFileR.Size);
|
|
x := hCols[5].Left + hCols[5].Width - 2 - TextWidth(s);
|
|
TextOut(x, aRect.Top + 2, s);
|
|
s := DateTimeToStr(r.FFileR.ModificationTime);
|
|
with hCols[4] do
|
|
TextRect(Rect(Left, aRect.Top, Left + Width, aRect.Bottom),
|
|
Left + 2, aRect.Top + 2, s)
|
|
end;
|
|
ImageList1.Draw(MainDrawGrid.Canvas,
|
|
hCols[3].Left + (hCols[3].Width - ImageList1.Width) div 2 - 2,
|
|
(aRect.Top + aRect.Bottom - ImageList1.Height - 1) div 2, Ord(r.FAction));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.MainDrawGridMouseDown(Sender: TObject;
|
|
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
var
|
|
c, r: Integer;
|
|
sr: TFileSyncRec;
|
|
ca: TSyncRecState;
|
|
begin
|
|
MainDrawGrid.MouseToCell(X, Y, c, r);
|
|
if (r < 0) or (r >= FVisibleItems.Count)
|
|
or (x - 2 < hCols[3].Left)
|
|
or (x - 2 > hCols[3].Left + hCols[3].Width)
|
|
then
|
|
Exit;
|
|
sr := TFileSyncRec(FVisibleItems.Objects[r]);
|
|
if not Assigned(sr) or (sr.FState = srsEqual) then Exit;
|
|
ca := sr.FAction;
|
|
case ca of
|
|
srsNotEq:
|
|
ca := srsCopyRight;
|
|
srsCopyRight:
|
|
if Assigned(sr.FFileR) then
|
|
ca := srsCopyLeft
|
|
else
|
|
ca := srsDoNothing;
|
|
srsCopyLeft:
|
|
if Assigned(sr.FFileL) then
|
|
ca := srsNotEq
|
|
else
|
|
ca := srsDoNothing;
|
|
srsDeleteRight:
|
|
ca := srsDoNothing;
|
|
srsDoNothing:
|
|
if Assigned(sr.FFileL) then
|
|
ca := srsCopyRight
|
|
else
|
|
ca := FFileExists;
|
|
end;
|
|
sr.FAction := ca;
|
|
MainDrawGrid.InvalidateRow(r);
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FormKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
begin
|
|
if Key = VK_ESCAPE then
|
|
begin
|
|
Key := 0;
|
|
if not FCancel then
|
|
FCancel := True
|
|
else
|
|
Close;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.HeaderDGHeaderClick(Sender: TObject;
|
|
IsColumn: Boolean; Index: Integer);
|
|
begin
|
|
if (Index <> 3) and (Index <= 6) then
|
|
SortIndex := Index;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.HeaderDGHeaderSizing(sender: TObject;
|
|
const IsColumn: boolean; const aIndex, aSize: Integer);
|
|
begin
|
|
RecalcHeaderCols;
|
|
MainDrawGrid.Invalidate;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FilterSpeedButtonClick(Sender: TObject);
|
|
begin
|
|
FillFoundItemsDG
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.MenuItemViewClick(Sender: TObject);
|
|
var
|
|
r: Integer;
|
|
f: TFile = nil;
|
|
sr: TFileSyncRec;
|
|
begin
|
|
r := MainDrawGrid.Row;
|
|
if (r < 0) or (r >= FVisibleItems.Count) then Exit;
|
|
sr := TFileSyncRec(FVisibleItems.Objects[r]);
|
|
if Assigned(sr) then
|
|
begin
|
|
if Sender = MenuItemViewLeft then
|
|
f := sr.FFileL
|
|
else if Sender = MenuItemViewRight then begin
|
|
f := sr.FFileR;
|
|
end;
|
|
if Assigned(f) then ShowViewerByGlob(f.FullPath);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.SetSortIndex(AValue: Integer);
|
|
var
|
|
s: string;
|
|
begin
|
|
if AValue = FSortIndex then
|
|
begin
|
|
s := HeaderDG.Columns[AValue].Title.Caption;
|
|
UTF8Delete(s, 1, 1);
|
|
FSortDesc := not FSortDesc;
|
|
if FSortDesc then s := '↑' + s else s := '↓' + s;
|
|
HeaderDG.Columns[AValue].Title.Caption := s;
|
|
SortFoundItems;
|
|
FillFoundItemsDG;
|
|
end else begin
|
|
if FSortIndex >= 0 then
|
|
begin
|
|
s := HeaderDG.Columns[FSortIndex].Title.Caption;
|
|
UTF8Delete(s, 1, 1);
|
|
HeaderDG.Columns[FSortIndex].Title.Caption := s;
|
|
end;
|
|
FSortIndex := AValue;
|
|
FSortDesc := False;
|
|
with HeaderDG.Columns[FSortIndex].Title do
|
|
Caption := '↓' + Caption;
|
|
SortFoundItems;
|
|
FillFoundItemsDG;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.ClearFoundItems;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
for i := 0 to FFoundItems.Count - 1 do
|
|
with TStringList(FFoundItems.Objects[i]) do
|
|
begin
|
|
for j := 0 to Count - 1 do
|
|
Objects[j].Free;
|
|
Clear;
|
|
end;
|
|
FFoundItems.Clear;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.Compare;
|
|
begin
|
|
TopPanel.Enabled := False;
|
|
try
|
|
ClearFoundItems;
|
|
MainDrawGrid.RowCount := 0;
|
|
ScanDirs;
|
|
FillFoundItemsDG;
|
|
finally
|
|
TopPanel.Enabled := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.FillFoundItemsDG;
|
|
|
|
procedure CalcStat;
|
|
var
|
|
i: Integer;
|
|
r: TFileSyncRec;
|
|
begin
|
|
Ftotal := 0;
|
|
Fequal := 0;
|
|
Fnoneq := 0;
|
|
FuniqueL := 0;
|
|
FuniqueR := 0;
|
|
for i := 0 to FVisibleItems.Count - 1 do
|
|
begin
|
|
r := TFileSyncRec(FVisibleItems.Objects[i]);
|
|
if Assigned(r) then
|
|
begin
|
|
Inc(Ftotal);
|
|
if Assigned(r.FFileL) and not Assigned(r.FFileR) then Inc(FuniqueL) else
|
|
if Assigned(r.FFileR) and not Assigned(r.FFileL) then Inc(FuniqueR);
|
|
if r.FState = srsEqual then Inc(Fequal) else
|
|
if r.FState = srsNotEq then Inc(Fnoneq);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
InitVisibleItems;
|
|
MainDrawGrid.ColCount := 1;
|
|
MainDrawGrid.RowCount := FVisibleItems.Count;
|
|
MainDrawGrid.Invalidate;
|
|
CalcStat;
|
|
UpdateStatusBar;
|
|
if FVisibleItems.Count > 0 then
|
|
begin
|
|
btnCompare.Default := False;
|
|
btnSynchronize.Enabled := True;
|
|
btnSynchronize.Default := True;
|
|
end else begin
|
|
btnCompare.Default := True;
|
|
btnSynchronize.Enabled := False;
|
|
btnSynchronize.Default := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.InitVisibleItems;
|
|
var
|
|
i, j: Integer;
|
|
filter: record
|
|
copyLeft, copyRight, eq, neq: Boolean;
|
|
dup, single: Boolean;
|
|
end;
|
|
r: TFileSyncRec;
|
|
|
|
begin
|
|
if not Assigned(FVisibleItems) then
|
|
FVisibleItems := TStringList.Create
|
|
else
|
|
FVisibleItems.Clear;
|
|
{ init filter }
|
|
with filter do
|
|
begin
|
|
copyLeft := sbCopyLeft.Down;
|
|
copyRight := sbCopyRight.Down;
|
|
eq := sbEqual.Down;
|
|
neq := sbNotEqual.Down;
|
|
dup := sbDuplicates.Down;
|
|
single := sbSingles.Down;
|
|
end;
|
|
for i := 0 to FFoundItems.Count - 1 do
|
|
begin
|
|
if FFoundItems[i] <> '' then
|
|
FVisibleItems.Add(AppendPathDelim(FFoundItems[i]));
|
|
with TStringList(FFoundItems.Objects[i]) do
|
|
for j := 0 to Count - 1 do
|
|
begin
|
|
{ check filter }
|
|
r := TFileSyncRec(Objects[j]);
|
|
if ((Assigned(r.FFileL) <> Assigned(r.FFileR)) and filter.single or
|
|
(Assigned(r.FFileL) = Assigned(r.FFileR)) and filter.dup)
|
|
and
|
|
((r.FState = srsCopyLeft) and filter.copyLeft or
|
|
(r.FState = srsCopyRight) and filter.copyRight or
|
|
(r.FState = srsDeleteRight) and filter.copyLeft or
|
|
(r.FState = srsEqual) and filter.eq or
|
|
(r.FState = srsNotEq) and filter.neq or
|
|
(r.FState = srsUnknown))
|
|
then
|
|
FVisibleItems.AddObject(Strings[j], Objects[j]);
|
|
end;
|
|
end;
|
|
{ remove empty dirs after filtering }
|
|
for i := FVisibleItems.Count - 1 downto 0 do
|
|
if (FVisibleItems.Objects[i] = nil)
|
|
and ((i + 1 >= FVisibleItems.Count) or (FVisibleItems.Objects[i + 1] = nil)) then
|
|
FVisibleItems.Delete(i);
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.RecalcHeaderCols;
|
|
var
|
|
i, l: Integer;
|
|
begin
|
|
l := 0;
|
|
for i := 0 to 6 do
|
|
with hCols[i] do
|
|
begin
|
|
Left := l;
|
|
Width := HeaderDG.Columns[i].Width;
|
|
l := l + Width;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.ScanDirs;
|
|
|
|
var
|
|
BaseDirL, BaseDirR: string;
|
|
MasksStr: string;
|
|
ignoreDate, Subdirs, ByContent: Boolean;
|
|
|
|
procedure ScanDir(dir: string);
|
|
|
|
procedure ProcessOneSide(it, dirs: TStringList; sideLeft: Boolean);
|
|
var
|
|
fs: TFiles;
|
|
i, j: Integer;
|
|
f: TFile;
|
|
r: TFileSyncRec;
|
|
begin
|
|
if sideLeft then
|
|
fs := FFileSourceL.GetFiles(BaseDirL + dir)
|
|
else
|
|
fs := FFileSourceR.GetFiles(BaseDirR + dir);
|
|
try
|
|
for i := 0 to fs.Count - 1 do
|
|
begin
|
|
f := fs.Items[i];
|
|
if not f.IsDirectory and MatchesMaskList(f.Name, MasksStr) then
|
|
begin
|
|
j := it.IndexOf(f.Name);
|
|
if j < 0 then
|
|
r := TFileSyncRec.Create(Self, dir)
|
|
else
|
|
r := TFileSyncRec(it.Objects[j]);
|
|
if sideLeft then
|
|
begin
|
|
r.FFileL := f.Clone;
|
|
r.UpdateState(ignoreDate);
|
|
end else begin
|
|
r.FFileR := f.Clone;
|
|
r.UpdateState(ignoreDate);
|
|
if ByContent and (r.FState = srsEqual) and (r.FFileR.Size > 0) then
|
|
begin
|
|
r.FAction := srsUnknown;
|
|
r.FState := srsUnknown;
|
|
end;
|
|
end;
|
|
it.AddObject(f.Name, r);
|
|
end else
|
|
if (f.NameNoExt <> '.') and (f.NameNoExt <> '..') then
|
|
dirs.Add(f.Name);
|
|
end;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i, j, tot: Integer;
|
|
it: TStringList;
|
|
dirsLeft, dirsRight: TStringList;
|
|
d: string;
|
|
begin
|
|
i := FFoundItems.IndexOf(dir);
|
|
if i < 0 then
|
|
begin
|
|
it := TStringList.Create;
|
|
it.Sorted := True;
|
|
FFoundItems.AddObject(dir, it);
|
|
end else
|
|
it := TStringList(FFoundItems.Objects[i]);
|
|
if dir <> '' then dir := AppendPathDelim(dir);
|
|
dirsLeft := TStringList.Create;
|
|
dirsLeft.Sorted := True;
|
|
dirsRight := TStringList.Create;
|
|
dirsRight.Sorted := True;
|
|
try
|
|
Application.ProcessMessages;
|
|
if FCancel then Exit;
|
|
ProcessOneSide(it, dirsLeft, True);
|
|
ProcessOneSide(it, dirsRight, False);
|
|
SortFoundItems(it);
|
|
if not Subdirs then Exit;
|
|
tot := dirsLeft.Count + dirsRight.Count;
|
|
for i := 0 to dirsLeft.Count - 1 do
|
|
begin
|
|
if dir = '' then
|
|
StatusBar1.Panels[0].Text :=
|
|
Format(rsComparingPercent, [i * 100 div tot]);
|
|
d := dirsLeft[i];
|
|
ScanDir(dir + d);
|
|
j := dirsRight.IndexOf(d);
|
|
if j >= 0 then
|
|
begin
|
|
dirsRight.Delete(j);
|
|
Dec(tot);
|
|
end
|
|
end;
|
|
for i := 0 to dirsRight.Count - 1 do
|
|
begin
|
|
if dir = '' then
|
|
StatusBar1.Panels[0].Text :=
|
|
Format(rsComparingPercent, [(dirsLeft.Count + i) * 100 div tot]);
|
|
d := dirsRight[i];
|
|
ScanDir(dir + d);
|
|
end;
|
|
finally
|
|
dirsLeft.Free;
|
|
dirsRight.Free;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
FCancel := False;
|
|
FCmpFileSourceL := FFileSourceL;
|
|
FCmpFileSourceR := FFileSourceR;
|
|
MasksStr := cbExtFilter.Text;
|
|
BaseDirL := AppendPathDelim(edPath1.Text);
|
|
if (FAddressL <> '') and (Copy(BaseDirL, 1, Length(FAddressL)) = FAddressL) then
|
|
Delete(BaseDirL, 1, Length(FAddressL));
|
|
BaseDirR := AppendPathDelim(edPath2.Text);
|
|
if (FAddressR <> '') and (Copy(BaseDirR, 1, Length(FAddressR)) = FAddressR) then
|
|
Delete(BaseDirR, 1, Length(FAddressR));
|
|
FCmpFilePathL := BaseDirL;
|
|
FCmpFilePathR := BaseDirR;
|
|
ignoreDate := chkIgnoreDate.Checked;
|
|
Subdirs := chkSubDirs.Checked;
|
|
ByContent := chkByContent.Checked;
|
|
if chkAsymmetric.Checked then
|
|
FFileExists:= srsDeleteRight
|
|
else begin
|
|
FFileExists:= srsCopyLeft;
|
|
end;
|
|
ScanDir('');
|
|
if FCancel then Exit;
|
|
if (FFoundItems.Count > 0) and chkByContent.Checked then
|
|
CheckContentThread := TCheckContentThread.Create(Self);
|
|
FCancel := True;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.SortFoundItems;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if FSortIndex < 0 then Exit;
|
|
for i := 0 to FFoundItems.Count - 1 do
|
|
SortFoundItems(TStringList(FFoundItems.Objects[i]));
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.SortFoundItems(sl: TStringList);
|
|
|
|
function CompareFn(sl: TStringList; i, j: Integer): Integer;
|
|
var
|
|
r1, r2: TFileSyncRec;
|
|
begin
|
|
r1 := TFileSyncRec(sl.Objects[i]);
|
|
r2 := TFileSyncRec(sl.Objects[j]);
|
|
case FSortIndex of
|
|
0:
|
|
if Assigned(r1.FFileL) <> Assigned(r2.FFileL) then
|
|
Result := Ord(Assigned(r1.FFileL)) - Ord(Assigned(r2.FFileL))
|
|
else
|
|
if Assigned(r1.FFileL) then
|
|
Result := UTF8CompareStr(r1.FFileL.Name, r2.FFileL.Name)
|
|
else
|
|
Result := 0;
|
|
1:
|
|
if (Assigned(r1.FFileL) < Assigned(r2.FFileL))
|
|
or Assigned(r2.FFileL) and (r1.FFileL.Size < r2.FFileL.Size) then
|
|
Result := -1
|
|
else
|
|
if (Assigned(r1.FFileL) > Assigned(r2.FFileL))
|
|
or Assigned(r1.FFileL) and (r1.FFileL.Size > r2.FFileL.Size) then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
2:
|
|
if (Assigned(r1.FFileL) < Assigned(r2.FFileL))
|
|
or Assigned(r2.FFileL)
|
|
and (r1.FFileL.ModificationTime < r2.FFileL.ModificationTime) then
|
|
Result := -1
|
|
else
|
|
if (Assigned(r1.FFileL) > Assigned(r2.FFileL))
|
|
or Assigned(r1.FFileL)
|
|
and (r1.FFileL.ModificationTime > r2.FFileL.ModificationTime) then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
4:
|
|
if (Assigned(r1.FFileR) < Assigned(r2.FFileR))
|
|
or Assigned(r2.FFileR)
|
|
and (r1.FFileR.ModificationTime < r2.FFileR.ModificationTime) then
|
|
Result := -1
|
|
else
|
|
if (Assigned(r1.FFileR) > Assigned(r2.FFileR))
|
|
or Assigned(r1.FFileR)
|
|
and (r1.FFileR.ModificationTime > r2.FFileR.ModificationTime) then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
5:
|
|
if (Assigned(r1.FFileR) < Assigned(r2.FFileR))
|
|
or Assigned(r2.FFileR) and (r1.FFileR.Size < r2.FFileR.Size) then
|
|
Result := -1
|
|
else
|
|
if (Assigned(r1.FFileR) > Assigned(r2.FFileR))
|
|
or Assigned(r1.FFileR) and (r1.FFileR.Size > r2.FFileR.Size) then
|
|
Result := 1
|
|
else
|
|
Result := 0;
|
|
6:
|
|
if Assigned(r1.FFileR) <> Assigned(r2.FFileR) then
|
|
Result := Ord(Assigned(r1.FFileR)) - Ord(Assigned(r2.FFileR))
|
|
else
|
|
if Assigned(r1.FFileR) then
|
|
Result := UTF8CompareStr(r1.FFileR.Name, r2.FFileR.Name)
|
|
else
|
|
Result := 0;
|
|
end;
|
|
if FSortDesc then
|
|
Result := -Result;
|
|
end;
|
|
|
|
procedure QuickSort(L, R: Integer; sl: TStringList);
|
|
var
|
|
Pivot, vL, vR: Integer;
|
|
begin
|
|
if R - L <= 1 then begin // a little bit of time saver
|
|
if L < R then
|
|
if CompareFn(sl, L, R) > 0 then
|
|
sl.Exchange(L, R);
|
|
Exit;
|
|
end;
|
|
|
|
vL := L;
|
|
vR := R;
|
|
|
|
Pivot := L + Random(R - L); // they say random is best
|
|
|
|
while vL < vR do begin
|
|
while (vL < Pivot) and (CompareFn(sl, vL, Pivot) <= 0) do
|
|
Inc(vL);
|
|
|
|
while (vR > Pivot) and (CompareFn(sl, vR, Pivot) > 0) do
|
|
Dec(vR);
|
|
|
|
sl.Exchange(vL, vR);
|
|
|
|
if Pivot = vL then // swap pivot if we just hit it from one side
|
|
Pivot := vR
|
|
else if Pivot = vR then
|
|
Pivot := vL;
|
|
end;
|
|
|
|
if Pivot - 1 >= L then
|
|
QuickSort(L, Pivot - 1, sl);
|
|
if Pivot + 1 <= R then
|
|
QuickSort(Pivot + 1, R, sl);
|
|
end;
|
|
|
|
begin
|
|
QuickSort(0, sl.Count - 1, sl);
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.UpdateStatusBar;
|
|
var s: string;
|
|
begin
|
|
s := Format(rsFilesFound, [Ftotal, Fequal, Fnoneq, FuniqueL, FuniqueR]);
|
|
if Assigned(CheckContentThread)
|
|
and not TCheckContentThread(CheckContentThread).Done then
|
|
s := s + ' ...';
|
|
StatusBar1.Panels[0].Text := s;
|
|
end;
|
|
|
|
procedure TfrmSyncDirsDlg.StopCheckContentThread;
|
|
begin
|
|
if Assigned(CheckContentThread) then
|
|
begin
|
|
with TCheckContentThread(CheckContentThread) do
|
|
begin
|
|
Terminate;
|
|
WaitFor;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TfrmSyncDirsDlg.Create(AOwner: TComponent; FileView1,
|
|
FileView2: TFileView);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FFoundItems := TStringList.Create;
|
|
FFoundItems.Sorted := True;
|
|
FFileSourceL := FileView1.FileSource;
|
|
FFileSourceR := FileView2.FileSource;
|
|
FAddressL := FileView1.CurrentAddress;
|
|
FAddressR := FileView2.CurrentAddress;
|
|
with FileView1 do
|
|
edPath1.Text := FAddressL + CurrentPath;
|
|
with FileView2 do
|
|
edPath2.Text := FAddressR + CurrentPath;
|
|
RecalcHeaderCols;
|
|
MainDrawGrid.DoubleBuffered := True;
|
|
MainDrawGrid.Font.Bold := True;
|
|
FSortIndex := -1;
|
|
SortIndex := 0;
|
|
FCancel := True;
|
|
FSortDesc := False;
|
|
MainDrawGrid.RowCount := 0;
|
|
chkAsymmetric.Enabled := fsoDelete in FileView2.FileSource.GetOperationsTypes;
|
|
FFileSourceOperationMessageBoxesUI := TFileSourceOperationMessageBoxesUI.Create;
|
|
if (FFileSourceL.IsClass(TFileSystemFileSource)) and (FFileSourceR.IsClass(TFileSystemFileSource)) then
|
|
begin
|
|
FNtfsShift := gNtfsHourTimeDelay and NtfsHourTimeDelay(FileView1.CurrentPath, FileView2.CurrentPath);
|
|
end;
|
|
end;
|
|
|
|
destructor TfrmSyncDirsDlg.Destroy;
|
|
begin
|
|
FFileSourceOperationMessageBoxesUI.Free;
|
|
FVisibleItems.Free;
|
|
ClearFoundItems;
|
|
FFoundItems.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|