unit uSynchronizeDirectories;
{$INCLUDE 'DirSync3.inc'}

{.$DEFINE LOG_FILECOPY}
{.$DEFINE LOG_DIRECTORYCOPY}

interface

{$IFNDEF DEBUGLOG}
  {$UNDEF LOG_FILECOPY}
  {$UNDEF LOG_DIRECTORYCOPY}
{$ENDIF}

uses
  SysUtils, Windows, Classes,
  {$IFDEF FAR3} Plugin3, {$ELSE} PluginW, {$ENDIF} FarKeysW, FarColor,
  uSystem, uFiles, uFAR, uLog,
  uDirSyncConsts, uMessages, uSynchronizeDirectoriesDialog, uConfirmations,
  uOptions, uDifferences, uSynchronizeUI, uSupportFunctions;

type
  TSynchronizeTask = class;

  TSynchronizeTask = class(TFarEditorManaged)
  private
    fLeftDirectory: string;
    fRightDirectory: string;
    fProgress: TFarProgress;
    fCopyFileSourceFileName: string;
    fCopyFileDestinationFileName: string;
    fCopyFileAborted: boolean;
    fSyncUI: TSynchronizeUI;
  protected
    function GetFileAttributes(const FileName: string; out Attributes: DWORD): boolean;
    function LoadFiles(out List: TDifferenceList; const FileName: string): boolean;
    procedure ShowProgress(const SourceFileName, DestinationFileName: string; Progress, Max: int64; Msg1, Msg2: TMessages);
    procedure ShowFileCopyProgress(Progress, Max: int64);
    function DeleteDirectoryMissingOnTheOtherPanel(const FileName: string; DeleteOnLeft: boolean): boolean;
    function DeleteFileMissingOnTheOtherPanel(const FileName: string; DeleteOnLeft: boolean): boolean;
    function InternalCopyFileWithProgress(const SourceFileName, DestinationFileName: string; out ErrorCode: DWORD): boolean;
    function CopyFile(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
    function CopyDirectory(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
    function SyncFile(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
    procedure EscapePressedEvent(Sender: TObject; var Value: boolean);
    procedure SetupKeyBar;
    procedure BeforeClose(var CanClose: boolean; var Result: TIntPtr); override;
    procedure AfterShowEditor; override;
    property SyncUI: TSynchronizeUI read fSyncUI;
  public
    constructor Create(const LeftDirectory, RightDirectory: string);
    destructor Destroy; override;
    function ShowEditor(MoveCursorToTheFirstDifference: boolean): boolean;
    function Execute(out ReturnToEditor: boolean): boolean;
    procedure Edit(const FileName: string);
    procedure VisualCompare(LeftFileName, RightFileName: string);
    property LeftDirectory: string read fLeftDirectory;
    property RightDirectory: string read fRightDirectory;
  end;

implementation

uses
  uDirSync;

{ TSyncFileInfo }

constructor TSynchronizeTask.Create(const LeftDirectory, RightDirectory: string);
var
  Options: TDirSyncOptions;
  TextStream: TTextStream;
begin
  inherited Create(TFarUtils.TempFileName);
  {$IFDEF DEBUGLOG}
  //Log('START TSynchronizeTask.Create("%s", "%s")', [LeftDirectory, RightDirectory]);
  {$ENDIF}
  fLeftDirectory := IncludeTrailingPathDelimiter(LeftDirectory);
  fRightDirectory := IncludeTrailingPathDelimiter(RightDirectory);
  fSyncUI := TSynchronizeUI.Create;
  fProgress := nil;
  Options := TDirSyncOptions.Create;
  try
    Options.Load;
    SyncUI.SilentMode := Options.SilentModeByDefault;
    SyncUI.GlobalRemember := Options.GlobalRemember;
    TextStream := TTextStream.Create(Self.FileName, fmCreate);
    try
      try
        TextStream.Encoding := {$IFDEF UNICODE} feUTF8 {$ELSE} feAnsi {$ENDIF} ;
        TextStream.WriteLine(Format(GetMsgPas(MReportFileTitle), [RemoveNTSchematicsFromFileName(LeftDirectory), RemoveNTSchematicsFromFileName(RightDirectory)]));
        if Options.ShowDescriptionIsSyncFile then begin
          TextStream.WriteLine(GetMsgPas(MReportFileNotes1));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF1));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF2));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF3));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF5));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF6));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsCTRLF1));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsCTRLF2));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsCTRLF3));
          TextStream.WriteLine(GetMsgPas(MReportFileControlsALTF1Notes));
          TextStream.WriteLine(GetMsgPas(MReportFileNotes2));
        end;
        TextStream.WriteLine('');
      finally
        FreeAndNil(TextStream);
      end;
    except
      if FileExists(Self.FileName) then
        SysUtils.DeleteFile(Self.FileName);
      Raise;
    end;
  finally
    FreeAndNil(Options);
    end;
end;

function TSynchronizeTask.DeleteDirectoryMissingOnTheOtherPanel(const FileName: string; DeleteOnLeft: boolean): boolean;
var
  Attr: DWORD;
  SR: TSearchRec;
  Dir: string;
  ContentDeleted: boolean;
begin
  Result := False;
  ShowProgress(FileName, '', 0, 1, MDeletingDirectory, MNoLngStringDefined);
  //Log('Deleting directory "%s"', [FileName]);
  repeat
    if not GetFileAttributes(FileName, Attr) then begin
      //Log('- File does not exist');
      Result := True;
      Break;
    end
    else begin
      if not SyncUI.AskDeleteConfirmation(FileName, DeleteOnLeft) then begin
        //Log('- User did not allow deleting the destination file');
        Break;
      end;
      // The user allowed me to delete the directory. First delete the content.
      Dir := IncludeTrailingPathDelimiter(FileName);
      //Log('Searching "%s*.*"', [Dir]);
      ContentDeleted := True;
      if FindFirst(Dir + '*.*', faAnyFile, SR) = 0 then
        try
          repeat
            //Log('Found item: "%s" (directory=%d)', [SR.Name, Integer((SR.Attr and faDirectory) <> 0)]);
            if (SR.Attr and faDirectory) <> 0 then begin
              if (SR.Name <> '.') and (SR.Name <> '..') then begin
                if not DeleteDirectoryMissingOnTheOtherPanel(Dir + SR.Name, DeleteOnLeft) then begin
                  ContentDeleted := False;
                  //Log('Failed to delete');
                end;
              end;
            end
            else begin
              if not DeleteFileMissingOnTheOtherPanel(Dir + SR.Name, DeleteOnLeft) then begin
                ContentDeleted := False;
                //Log('Failed to delete');
              end;
            end;
          until FindNext(SR) <> 0;
        finally
          SysUtils.FindClose(SR);
        end;
      // Then delete the directory itself
      if ContentDeleted then begin
        //Log('Content deleted, deleting "%s"', [FileName]);
        SetFileAttributes(PChar(FileName), Attr and (not (FILE_ATTRIBUTE_READONLY or FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM)));
        if RemoveDirectory(PChar(FileName)) then begin
          //Log('- Deleted successfully');
          Result := True;
          Break;
        end;
      end;
      // If I got here, then the directory couldn't be deleted
      if not SyncUI.AskRetryDelete(MFailedEraseDir, FileName) then
        Break;
    end;
  until False;
  //Log('Result: %d', [Integer(Result)]);
end;

function TSynchronizeTask.DeleteFileMissingOnTheOtherPanel(const FileName: string; DeleteOnLeft: boolean): boolean;
var
  Attr: DWORD;
begin
  Result := False;
  ShowProgress(FileName, '', 0, 1, MDeletingFile, MNoLngStringDefined);
  //Log('Deleting file "%s"', [FileName]);
  repeat
    if not GetFileAttributes(FileName, Attr) then begin
      //Log('- File does not exist');
      Result := True;
      Break;
    end
    else begin
      if not SyncUI.AskDeleteConfirmation(FileName, DeleteOnLeft) then begin
        //Log('- User did not allow deleting the destination file');
        Break;
      end;
      SetFileAttributes(PChar(FileName), Attr and (not (FILE_ATTRIBUTE_READONLY or FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM)));
      if DeleteFile(PChar(FileName)) then begin
        //Log('- Deleted successfully');
        Result := True;
        Break;
      end
      else begin
        if not SyncUI.AskRetryDelete(MFailedEraseFile, FileName) then
          Break;
      end;
    end;
  until False;
  //Log('- Result: %d', [Integer(Result)]);
end;

destructor TSynchronizeTask.Destroy;
begin
  FreeAndNil(fSyncUI);
  FreeAndNil(fProgress);
  if FileName <> '' then
    if FileExists(FileName) then
      SysUtils.DeleteFile(FileName);
  inherited;
end;

procedure TSynchronizeTask.Edit(const FileName: string);
begin
  FarApi.Editor(PFarChar(FileName), GetMsg(MTitle), 0, 0, -1, -1,
    EF_NONMODAL or EF_IMMEDIATERETURN,
    0, 1, {$IFDEF FAR3_UP} CP_DEFAULT {$ELSE} CP_AUTODETECT {$ENDIF}
  );
end;

procedure TSynchronizeTask.EscapePressedEvent(Sender: TObject; var Value: boolean);
begin
  Value := FarMessage([MEscTitle, MEscBody, MOk, MCancel], FMSG_WARNING, 2) = 0;
end;

function TSynchronizeTask.GetFileAttributes(const FileName: string; out Attributes: DWORD): boolean;
var
  LastError: DWORD;
  SR: TSearchRec;
begin
  Attributes := Windows.GetFileAttributes(PChar(FileName));
  if Attributes <> INVALID_FILE_ATTRIBUTES then
    Result := True
  else begin
    LastError := GetLastError;
    if (LastError = ERROR_FILE_NOT_FOUND) or (LastError = ERROR_PATH_NOT_FOUND) or (LastError = ERROR_INVALID_NAME) then
      Result := False
    else
      if FindFirst(FileName, faAnyFile, SR) = 0 then
        try
          Attributes := SR.Attr;
          Result := True;
        finally
          SysUtils.FindClose(SR);
        end
      else
        Result := False;
  end;
end;

function CopyProgressRoutine(TotalFileSize, TotalBytesTransferred, StreamSize, StreamBytesTransferred: int64; dwStreamNumber, dwCallbackReason: DWORD; hSourceFile, hDestinationFile: THandle; lpData: TSynchronizeTask): DWORD; stdcall;
begin
  Result := PROGRESS_CONTINUE;
  if lpData <> nil then
    try
      lpData.ShowFileCopyProgress(TotalBytesTransferred, TotalFileSize);
    except
      on EAbort do
        begin
          lpData.fCopyFileAborted := True;
          Result := PROGRESS_CANCEL;
        end;
      on Exception do
        Result := PROGRESS_CONTINUE;
    end;
end;

function TSynchronizeTask.InternalCopyFileWithProgress(const SourceFileName, DestinationFileName: string; out ErrorCode: DWORD): boolean;
var SrcAttr, DestAttr: DWORD;
begin
  {$IFDEF LOG_FILECOPY}
  Log('START InternalCopyFileWithProgress("%s", "%s")', [SourceFileName, DestinationFileName]);
  {$ENDIF}
  if GetFileAttributes(SourceFileName, SrcAttr) then begin
    if GetFileAttributes(DestinationFileName, DestAttr) then begin
      SetFileAttributes(PChar(DestinationFileName), DestAttr and (not (FILE_ATTRIBUTE_READONLY or FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_SYSTEM)));
      DeleteFile(PChar(DestinationFileName));
    end;
    fCopyFileSourceFileName := SourceFileName;
    fCopyFileDestinationFileName := DestinationFileName;
    fCopyFileAborted := False;
    Result := CopyFileEx(PChar(SourceFileName), PChar(DestinationFileName), @CopyProgressRoutine, Self, nil, 0);
    ErrorCode := GetLastError;
    if Result then
      SetFileAttributes(PChar(DestinationFileName), SrcAttr)
    else if fCopyFileAborted then
      Abort;
  end
  else
    Result := False;
  {$IFDEF LOG_FILECOPY}
  Log('END   InternalCopyFileWithProgress("%s", "%s", %d) -> %d', [SourceFileName, DestinationFileName, ErrorCode, Integer(Result)]);
  {$ENDIF}
end;

function TSynchronizeTask.LoadFiles(out List: TDifferenceList; const FileName: string): boolean;
begin
  Result := False;
  List := TDifferenceList.Create(True);
  try
    List.LoadFromFile(FileName);
    Result := True;
  finally
    if not Result then
      FreeAndNil(List);
  end;
end;

procedure TSynchronizeTask.SetupKeyBar;
{$IFDEF FAR3_UP}
const
  KEY_CTRL_F1: TFarKey = (VirtualKeyCode: VK_F1; ControlKeyState: LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED);
  KEY_CTRL_F2: TFarKey = (VirtualKeyCode: VK_F2; ControlKeyState: LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED);
  KEY_CTRL_F3: TFarKey = (VirtualKeyCode: VK_F3; ControlKeyState: LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED);
  KEY_ALT_F1: TFarKey = (VirtualKeyCode: VK_F1; ControlKeyState: LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED);
  KEY_ALT_F2: TFarKey = (VirtualKeyCode: VK_F2; ControlKeyState: LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED);
  KEY_ALT_F3: TFarKey = (VirtualKeyCode: VK_F3; ControlKeyState: LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED);
  KEY_ALT_F5: TFarKey = (VirtualKeyCode: VK_F5; ControlKeyState: LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED);
  KEY_ALT_F6: TFarKey = (VirtualKeyCode: VK_F6; ControlKeyState: LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED);
{$ENDIF}
var
  {$IFDEF FAR3_UP}
  SetKeyBarTitles: TFarSetKeyBarTitles;
  KeyBarTitles: TKeyBarTitles;
  KeyBarLabels: packed array[0..7] of TKeyBarLabel;
  {$ELSE}
  KeyBarTitles: TKeyBarTitles;
  {$ENDIF}
begin
  {$IFDEF FAR3_UP}
  KeyBarLabels[0].Key := KEY_CTRL_F1;
  KeyBarLabels[0].Text := GetMsg(MKeyEditLeft);
  KeyBarLabels[0].LongText := GetMsg(MKeyEditLeftLong);
  KeyBarLabels[1].Key := KEY_CTRL_F2;
  KeyBarLabels[1].Text := GetMsg(MKeyEditRight);
  KeyBarLabels[1].LongText := GetMsg(MKeyEditRightLong);
  KeyBarLabels[2].Key := KEY_ALT_F1;
  KeyBarLabels[2].Text := GetMsg(MKeyOverwriteLeft);
  KeyBarLabels[2].LongText := GetMsg(MKeyOverwriteLeftLong);
  KeyBarLabels[3].Key := KEY_ALT_F2;
  KeyBarLabels[3].Text := GetMsg(MKeyOverwriteRight);
  KeyBarLabels[3].LongText := GetMsg(MKeyOverwriteRightLong);
  KeyBarLabels[4].Key := KEY_ALT_F3;
  KeyBarLabels[4].Text := GetMsg(MKeyCompare);
  KeyBarLabels[4].LongText := GetMsg(MKeyCompareLong);
  KeyBarLabels[5].Key := KEY_CTRL_F3;
  KeyBarLabels[5].Text := GetMsg(MKeyFileInfo);
  KeyBarLabels[5].LongText := GetMsg(MKeyFileInfoLong);
  KeyBarLabels[6].Key := KEY_ALT_F5;
  KeyBarLabels[6].Text := GetMsg(MKeyFindNextToLeft);
  KeyBarLabels[6].LongText := GetMsg(MKeyFindNextToLeftLong);
  KeyBarLabels[7].Key := KEY_ALT_F6;
  KeyBarLabels[7].Text := GetMsg(MKeyFindNextToRight);
  KeyBarLabels[7].LongText := GetMsg(MKeyFindNextToRightLong);
  KeyBarTitles.CountLabels := Length(KeyBarLabels);
  KeyBarTitles.Labels := @KeyBarLabels[0];
  SetKeyBarTitles.StructSize := Sizeof(SetKeyBarTitles);
  SetKeyBarTitles.Titles := @KeyBarTitles;
  FarApi.EditorControl(Self.EditorID, ECTL_SETKEYBAR, 0, @SetKeyBarTitles);
  {$ELSE}
  FillChar(KeyBarTitles, Sizeof(KeyBarTitles), 0);
  KeyBarTitles.CtrlTitles[0] := GetMsg(MKeyEditLeft);
  KeyBarTitles.CtrlTitles[1] := GetMsg(MKeyEditRight);
  KeyBarTitles.CtrlTitles[2] := GetMsg(MKeyFileInfo);
  KeyBarTitles.AltTitles[0] := GetMsg(MKeyOverwriteLeft);
  KeyBarTitles.AltTitles[1] := GetMsg(MKeyOverwriteRight);
  KeyBarTitles.AltTitles[2] := GetMsg(MKeyCompare);
  KeyBarTitles.AltTitles[4] := GetMsg(MKeyFindNextToLeft);
  KeyBarTitles.AltTitles[5] := GetMsg(MKeyFindNextToRight);
  FarApi.EditorControl(ECTL_SETKEYBAR, @KeyBarTitles);
  {$ENDIF}
end;

function TSynchronizeTask.ShowEditor(MoveCursorToTheFirstDifference: boolean): boolean;
var
  StartingLine: integer;
  Str: TTextStream;
  Line: string;
  Item: TDifferenceItem;
begin
  Result := inherited ShowEditor(GetMsg(MTitle), 0, 0, -1, -1,
        EF_NONMODAL or EF_IMMEDIATERETURN or EF_DELETEONLYFILEONCLOSE or EF_DISABLEHISTORY {$IFDEF FAR3_UP} or EF_DISABLESAVEPOS {$ENDIF} ,
        0, 1, {$IFDEF UNICODE} CP_UTF8 {$ELSE} CP_ANSI {$ENDIF} );
  if Result and MoveCursorToTheFirstDifference then begin
    StartingLine := 0;
    Item := TDifferenceItem.Create;
    try
      Str := TTextStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
      try
        while Str.ReadLine(Line) do begin
          if Item.SetAsString(Line) then begin
            if StartingLine > 0 then begin
              TFarEditor.SetCursorPosition(EditorID, StartingLine);
            end;
            Break;
          end;
          Inc(StartingLine);
        end;
      finally
        FreeAndNil(Str);
      end;
    finally
      FreeAndNil(Item);
    end;
  end;
end;

procedure TSynchronizeTask.ShowFileCopyProgress(Progress, Max: int64);
begin
  ShowProgress(fCopyFileSourceFileName, fCopyFileDestinationFileName, Progress, Max, MCopying, MCopyingTo);
end;

procedure TSynchronizeTask.ShowProgress(const SourceFileName, DestinationFileName: string; Progress, Max: int64; Msg1, Msg2: TMessages);
begin
  if fProgress.NeedsRefresh then begin
    fProgress.Show(Integer(MTitle), Integer(Msg1), Integer(Msg2), RemoveNTSchematicsFromFileName(SourceFileName), RemoveNTSchematicsFromFileName(DestinationFileName), Progress, Max);
  end;
end;

procedure TSynchronizeTask.AfterShowEditor;
begin
  inherited;
  SetupKeyBar;
end;

procedure TSynchronizeTask.BeforeClose(var CanClose: boolean; var Result: TIntPtr);
var
  ReturnToEditor: boolean;
begin
  inherited;
  Result := 0;
  Execute(ReturnToEditor);
  if ReturnToEditor then
    CanClose := False;
end;

function TSynchronizeTask.CopyDirectory(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
const
  ATTRIBUTES_TO_COPY = FILE_ATTRIBUTE_ARCHIVE or FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_READONLY or FILE_ATTRIBUTE_SYSTEM;
var
  SourceAttr, DestAttr: DWORD;
  SourceDir, DestinationDir: string;
  SR: TSearchRec;
begin
  {$IFDEF LOG_DIRECTORYCOPY}
  Log('START CopyDirectory("%s", "%s", %d) START', [SourceFileName, DestinationFileName, Integer(FromRightToLeft)]);
  {$ENDIF}
  Result := False;
  ShowProgress(SourceFileName, DestinationFileName, 0, 1, MCopying, MCopyingTo);
  repeat
    if not GetFileAttributes(SourceFileName, SourceAttr) then begin
      if not SyncUI.AskRetryNotFound(MSourceNotFound, SourceFileName) then
        Break;
    end
    else if (SourceAttr and FILE_ATTRIBUTE_DIRECTORY) = 0 then begin
      if not SyncUI.AskRetryNotDirectory(MSourceIsNotDirectory, SourceFileName) then
        Break;
    end
    else begin
      if not GetFileAttributes(DestinationFileName, DestAttr) then begin
        ForceDirectories(DestinationFileName);
        if not GetFileAttributes(DestinationFileName, DestAttr) then begin
          if not SyncUI.AskRetryMakeDir(MCantCreateDirectory, DestinationFileName) then
            Break;
        end;
      end;
      if (DestAttr and FILE_ATTRIBUTE_DIRECTORY) = 0 then begin
        if not SyncUI.AskRetryNotDirectory(MDestinationIsNotDirectory, DestinationFileName) then
          Break;
      end;
      //Log('- Copying attributes');
      if (SourceAttr and ATTRIBUTES_TO_COPY) <> (DestAttr and ATTRIBUTES_TO_COPY) then
        SetFileAttributes(PChar(DestinationFileName), (DestAttr and (not ATTRIBUTES_TO_COPY)) or (SourceAttr and ATTRIBUTES_TO_COPY));
      //Log('- Copying files');
      Result := True;
      SourceDir := IncludeTrailingPathDelimiter(SourceFileName);
      DestinationDir := IncludeTrailingPathDelimiter(DestinationFileName);
      if FindFirst(SourceDir + '*.*', faAnyFile, SR) = 0 then
        try
          repeat
            if (SR.Attr and faDirectory) <> 0 then
              if (SR.Name = '.') or (SR.Name = '..') then
                Continue
              else
                CopyDirectory(SourceDir + SR.Name, DestinationDir + SR.Name, FromRightToLeft)
            else
              CopyFile(SourceDir + SR.Name, DestinationDir + SR.Name, FromRightToLeft);
          until FindNext(SR) <> 0;
        finally
          SysUtils.FindClose(SR);
        end;
      Break;
    end;
  until False;
  {$IFDEF LOG_DIRECTORYCOPY}
  Log('END   CopyDirectory("%s", "%s", %d) -> %d', [SourceFileName, DestinationFileName, Integer(FromRightToLeft), Integer(Result)]);
  {$ENDIF}
end;

function TSynchronizeTask.CopyFile(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
var
  SourceAttr, DestAttr, CopyError: DWORD;
  Handle: THandle;
  Msg: TMessages;
begin
  {$IFDEF LOG_FILECOPY}
  Log('START CopyFile("%s", "%s", %d) START', [SourceFileName, DestinationFileName, Integer(FromRightToLeft)]);
  {$ENDIF}
  Result := False;
  ShowProgress(SourceFileName, DestinationFileName, 0, 1, MCopying, MCopyingTo);
  repeat
    if not GetFileAttributes(SourceFileName, SourceAttr) then begin
      if not SyncUI.AskRetryNotFound(MSourceNotFound, SourceFileName) then
        Break;
    end
    else begin
      if GetFileAttributes(DestinationFileName, DestAttr) then
        if not SyncUI.AskOverwriteConfirmation(SourceFileName, DestinationFileName, FromRightToLeft) then begin
          {$IFDEF LOG_FILECOPY}
          Log('- User did not allow overwriting the destination file');
          {$ENDIF}
          Break;
        end;
      if InternalCopyFileWithProgress(SourceFileName, DestinationFileName, CopyError) then begin
        {$IFDEF LOG_FILECOPY}
        Log('- Copied successfully');
        {$ENDIF}
        Result := True;
        Break;
      end
      else begin
        {$IFDEF LOG_FILECOPY}
        Log('- Error %d while copying', [CopyError]);
        {$ENDIF}
        Msg := MFailedCopySrcFile;
        Handle := CreateFile(PChar(SourceFileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
        if Handle = INVALID_HANDLE_VALUE then
          Msg := MFailedOpenSrcFile
        else begin
          CloseHandle(Handle);
          Handle := CreateFile(PChar(DestinationFileName), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
          if Handle = INVALID_HANDLE_VALUE then
            if GetLastError = ERROR_FILE_NOT_FOUND then
              Msg := MFailedCreateDstFile
            else
              Msg := MFailedOpenDstFile
          else
            CloseHandle(Handle);
        end;
        {$IFDEF LOG_FILECOPY}
        Log('- Error cause: %s', [GetMsgPas(Msg)]);
        {$ENDIF}
				SetLastError(CopyError);
        if not SyncUI.AskRetryCopy(Msg, MFailedCopyDstFile, SourceFileName, DestinationFileName) then
          Break;
      end;
    end;
  until False;
  {$IFDEF LOG_FILECOPY}
  Log('END   CopyFile("%s", "%s", %d) -> %d', [SourceFileName, DestinationFileName, Integer(FromRightToLeft), Integer(Result)]);
  {$ENDIF}
end;

function TSynchronizeTask.Execute(out ReturnToEditor: boolean): boolean;
var
  List: TDifferenceList;
  DiffItem: TDifferenceItem;
  Dlg: TSynchronizeDirectoriesDialog;
  DiffType: TDifferenceType;
  i, Processed, Total: Integer;
  FN: string;
  OK, SyncAgain: Boolean;
  NewTask: TSynchronizeTask;
  DiffFile: TTextStream;
begin
  Result := False;
  ReturnToEditor := True;
  try
    //FarMessage(['DirSync', '', 'Files should be synchronized here:', PChar(FileName), PChar(LeftDirectory), PChar(RightDirectory), '', 'OK'], 0, 1);
    if LoadFiles(List, Self.FileName) then
      try
        if List.Count = 0 then begin
          ReturnToEditor := False;
        end
        else begin
          Dlg := TSynchronizeDirectoriesDialog.Create;
          try
            Dlg.SilentMode := SyncUI.SilentMode;
            Dlg.GlobalRemember := SyncUI.GlobalRemember;
            for DiffType := Low(TDifferenceType) to High(TDifferenceType) do
              Dlg.DiffTypePresent[DiffType] := List.HasDifference[DiffType];
            case Dlg.Execute of
              sdaCancel:
                begin
                  ReturnToEditor := False;
                end;
              sdaSynchronize:
                begin
                  ReturnToEditor := False;
                  //FarMessage(['DirSync', '', 'Files should be synchronized here:', PChar(IntToStr(List.Count) + ' files total'), '', 'OK'], 0, 1);
                  fProgress := TFarProgress.Create( {$IFDEF FAR3_UP} PLUGIN_GUID, DIALOG_READING_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} );
                  try
                    //fProgress.GranularityMSec := PROGRESS_GRANULARITY_MSEC;
                    fProgress.CheckForEscape := True;
                    fProgress.OnEscapePressed := EscapePressedEvent;
                    SyncUI.Clear;
                    if not Dlg.ConfirmOverwriteLeft then
                      SyncUI[''].OverwriteAction[True, False] := oaYes;
                    if not Dlg.ConfirmOverwriteRight then
                      SyncUI[''].OverwriteAction[False, False] := oaYes;
                    case Dlg.DifferentFilesAction of
                      TSynchronizeDirectoriesDialog.TDifferentFilesAction.dfaSkip:
                        SyncUI[''].DifferentFileAction := dfaSkip;
                      TSynchronizeDirectoriesDialog.TDifferentFilesAction.dfaCopyToLeft:
                        SyncUI[''].DifferentFileAction := dfaLeft;
                      TSynchronizeDirectoriesDialog.TDifferentFilesAction.dfaCopyToRight:
                        SyncUI[''].DifferentFileAction := dfaRight;
                    end;
                    SyncUI.SilentMode := Dlg.SilentMode;
                    SyncUI.GlobalRemember := Dlg.GlobalRemember;
                    try
                      repeat
                        i := 0;
                        Processed := 0;
                        Total := List.Count;
                        while i < List.Count do begin
                          TFarUtils.SetProgressState( {$IFDEF FAR3_UP} TBPS_NORMAL {$ELSE} PS_NORMAL {$ENDIF} , Processed, Total);
                          DiffItem := List[i];
                          FN := Trim(DiffItem.FileName);
                          OK := True; // If some difference types should be ignored, consider files with that difference successfully processed
                          case DiffItem.DifferenceType of
                            dtMissingLeft:
                              if Dlg.DeleteRightMissingOnLeft then
                                if DiffItem.IsDirectory then
                                  OK := DeleteDirectoryMissingOnTheOtherPanel(RightDirectory + FN, False)
                                else
                                  OK := DeleteFileMissingOnTheOtherPanel(RightDirectory + FN, False)
                              else if Dlg.CopyToLeftPanel then
                                if DiffItem.IsDirectory then
                                  OK := CopyDirectory(RightDirectory + FN, LeftDirectory + FN, True)
                                else
                                  OK := CopyFile(RightDirectory + FN, LeftDirectory + FN, True);
                            dtMissingRight:
                              if Dlg.DeleteLeftMissingOnRight then
                                if DiffItem.IsDirectory then
                                  OK := DeleteDirectoryMissingOnTheOtherPanel(LeftDirectory + FN, True)
                                else
                                  OK := DeleteFileMissingOnTheOtherPanel(LeftDirectory + FN, True)
                              else if Dlg.CopyToRightPanel then
                                if DiffItem.IsDirectory then
                                  OK := CopyDirectory(LeftDirectory + FN, RightDirectory + FN, False)
                                else
                                  OK := CopyFile(LeftDirectory + FN, RightDirectory + FN, False);
                            dtOlderLeft:
                              if Dlg.CopyToLeftPanel then
                                if DiffItem.IsDirectory then
                                  OK := CopyDirectory(RightDirectory + FN, LeftDirectory + FN, True)
                                else
                                  OK := CopyFile(RightDirectory + FN, LeftDirectory + FN, True);
                            dtOlderRight:
                              if Dlg.CopyToRightPanel then
                                if DiffItem.IsDirectory then
                                  OK := CopyDirectory(LeftDirectory + FN, RightDirectory + FN, False)
                                else
                                  OK := CopyFile(LeftDirectory + FN, RightDirectory + FN, False);
                            dtDifferent:
                              case Dlg.DifferentFilesAction of
                                TSynchronizeDirectoriesDialog.TDifferentFilesAction.dfaAsk:
                                  case SyncUI.AskWhatToDoWithDifferentFiles(LeftDirectory, RightDirectory, FN) of
                                    sfdSkip:
                                      OK := False;
                                    sfdCopyToLeft:
                                      OK := SyncFile(RightDirectory + FN, LeftDirectory + FN, True);
                                    sfdCopyToRight:
                                      OK := SyncFile(LeftDirectory + FN, RightDirectory + FN, False);
                                  end;
                                TSynchronizeDirectoriesDialog.TDifferentFilesAction.dfaSkip:
                                  OK := False;
                                dfaCopyToLeft:
                                  if DiffItem.IsDirectory then
                                    OK := CopyDirectory(RightDirectory + FN, LeftDirectory + FN, True)
                                  else
                                    OK := CopyFile(RightDirectory + FN, LeftDirectory + FN, True);
                                dfaCopyToRight:
                                  if DiffItem.IsDirectory then
                                    OK := CopyDirectory(LeftDirectory + FN, RightDirectory + FN, False)
                                  else
                                    OK := CopyFile(LeftDirectory + FN, RightDirectory + FN, False);
                              end;
                          end;
                          if OK then
                            List.Delete(i)
                          else
                            Inc(i);
                          Inc(Processed);
                        end;
                        SyncAgain := False;
                        if SyncUI.SilentMode then begin
                          //Log('Starting non-silent run');
                          SyncUI.SilentMode := False;
                          if List.Count > 0 then
                            SyncAgain := True;
                        end;
                      until not SyncAgain;
                    except
                      on EAbort do
                        ;
                      on Exception do
                        Raise;
                    end;
                    if List.Count > 0 then begin
                      NewTask := TSynchronizeTask.Create(Self.LeftDirectory, Self.RightDirectory);
                      try
                        DiffFile := TTextStream.Create(NewTask.FileName, fmOpenReadWrite or fmShareDenyWrite);
                        try
                          DiffFile.Position := DiffFile.Size;
                          for i := 0 to Pred(List.Count) do begin
                            DiffFile.WriteLine(List[i].AsString);
                          end;
                        finally
                          FreeAndNil(DiffFile);
                        end;
                        if NewTask.ShowEditor(False) then
                          NewTask := nil;
                      finally
                        FreeAndNil(NewTask);
                      end;
                    end;
                  finally
                    FreeAndNil(fProgress);
                  end;
                  TFarPanelInfo.Refresh(True);
                  TFarPanelInfo.Refresh(False);
                  Result := True;
                end;
            end;
          finally
            FreeAndNil(Dlg);
          end;
        end;
      finally
        FreeAndNil(List);
      end;
  except
    on E: EAbort do
      ; //Log('Aborted');
    on E: Exception do
      FarMessage([MTitle, MExtraString1, MExtraString2, MOk], [PFarChar(E.ClassName), PChar(E.Message)], FMSG_ERRORTYPE or FMSG_WARNING, 1);
  end;
end;

function TSynchronizeTask.SyncFile(const SourceFileName, DestinationFileName: string; FromRightToLeft: boolean): boolean;
var
  OldAction, NewAction: TRememberedAction;
begin
  //Log('SyncFile("%s", "%s", %d, %d, %d)', [SourceFileName, DestinationFileName, Integer(CanOverwrite), Integer(CanOverwriteReadOnly), Integer(FromRightToLeft)]);
  OldAction := TRememberedAction.Create('');
  try
    NewAction := SyncUI[''];
    OldAction.Assign(NewAction);
    try
      NewAction.OverwriteAction[FromRightToLeft, False] := oaYes;
        // Overwrite is implied - the user already said he wanted to overwrite Left or Right.
        // But that only applies to read-only files.
      Result := Self.CopyFile(SourceFileName, DestinationFileName, FromRightToLeft);
    finally
      NewAction.OverwriteAction[FromRightToLeft, False] := OldAction.OverwriteAction[FromRightToLeft, False];
    end;
  finally
    FreeAndNil(OldAction);
  end;
end;

procedure TSynchronizeTask.VisualCompare(LeftFileName, RightFileName: string);
begin
  SyncUI.VisualCompare(LeftFileName, RightFileName);
end;

end.
