瀏覽代碼

Factoring out common download code + Change: Last access time of downloaded files is no longer preserved

Source commit: f0d582dad37a7c48e72f4b805124f93dc0ccc050
Martin Prikryl 8 年之前
父節點
當前提交
642499f217

+ 7 - 0
source/core/FileSystems.h

@@ -65,6 +65,13 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError) = 0;
+  virtual void __fastcall DirectorySunk(
+    const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam) {};
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action) = 0;
   virtual void __fastcall CreateDirectory(const UnicodeString DirName) = 0;
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic) = 0;
   virtual void __fastcall DeleteFile(const UnicodeString FileName,

+ 36 - 274
source/core/FtpFileSystem.cpp

@@ -195,16 +195,6 @@ const UnicodeString AvblCommand(L"AVBL");
 const UnicodeString XQuotaCommand(L"XQUOTA");
 const UnicodeString DirectoryHasBytesPrefix(L"226-Directory has");
 //---------------------------------------------------------------------------
-struct TSinkFileParams
-{
-  UnicodeString TargetDir;
-  const TCopyParamType * CopyParam;
-  int Params;
-  TFileOperationProgressType * OperationProgress;
-  bool Skipped;
-  unsigned int Flags;
-};
-//---------------------------------------------------------------------------
 class TFileListHelper
 {
 public:
@@ -1530,286 +1520,59 @@ void __fastcall TFTPFileSystem::CopyToLocal(TStrings * FilesToCopy,
   TOnceDoneOperation & OnceDoneOperation)
 {
   Params &= ~cpAppend;
-  UnicodeString FullTargetDir = IncludeTrailingBackslash(TargetDir);
-
-  int Index = 0;
-  while (Index < FilesToCopy->Count && !OperationProgress->Cancel)
-  {
-    UnicodeString FileName = FilesToCopy->Strings[Index];
-    const TRemoteFile * File = dynamic_cast<const TRemoteFile *>(FilesToCopy->Objects[Index]);
-    bool Success = false;
-
-    try
-    {
-      try
-      {
-        SinkRobust(AbsolutePath(FileName, false), File, FullTargetDir, CopyParam, Params,
-          OperationProgress, tfFirstLevel);
-        Success = true;
-        FLastDataSent = Now();
-      }
-      catch(EScpSkipFile & E)
-      {
-        TSuspendFileOperationProgress Suspend(OperationProgress);
-        if (!FTerminal->HandleException(&E))
-        {
-          throw;
-        }
-      }
-    }
-    __finally
-    {
-      OperationProgress->Finish(FileName, Success, OnceDoneOperation);
-    }
-    Index++;
-  }
-}
-//---------------------------------------------------------------------------
-void __fastcall TFTPFileSystem::SinkRobust(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags)
-{
-  // the same in TSFTPFileSystem
-
-  TDownloadSessionAction Action(FTerminal->ActionLog);
-  TRobustOperationLoop RobustLoop(FTerminal, OperationProgress, &FFileTransferAny);
-
-  do
-  {
-    try
-    {
-      Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress,
-        Flags, Action);
-    }
-    catch(Exception & E)
-    {
-      if (!RobustLoop.TryReopen(E))
-      {
-        FTerminal->RollbackAction(Action, OperationProgress, &E);
-        throw;
-      }
-    }
 
-    if (RobustLoop.ShouldRetry())
-    {
-      OperationProgress->RollbackTransfer();
-      Action.Restart();
-      DebugAssert(File != NULL);
-      if (!File->IsDirectory)
-      {
-        // prevent overwrite confirmations
-        Params |= cpNoConfirmation;
-        Flags |= tfAutoResume;
-      }
-    }
-  }
-  while (RobustLoop.Retry());
+  FTerminal->DoCopyToLocal(
+    FilesToCopy, TargetDir, CopyParam, Params, OperationProgress, tfUseFileTransferAny, OnceDoneOperation);
 }
 //---------------------------------------------------------------------------
-void __fastcall TFTPFileSystem::Sink(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags,
-  TDownloadSessionAction & Action)
+void __fastcall TFTPFileSystem::Sink(
+  const UnicodeString & FileName, const TRemoteFile * File,
+  const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+  const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+  unsigned int Flags, TDownloadSessionAction & Action)
 {
-  UnicodeString OnlyFileName = UnixExtractFileName(FileName);
-
-  Action.FileName(FileName);
-
-  TFileMasks::TParams MaskParams;
-  DebugAssert(File);
-  MaskParams.Size = File->Size;
-  MaskParams.Modification = File->Modification;
+  AutoDetectTimeDifference(UnixExtractFileDir(FileName), CopyParam, Params);
 
-  UnicodeString BaseFileName = FTerminal->GetBaseFileName(FileName);
-  if (!CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams))
-  {
-    FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
-    THROW_SKIP_FILE_NULL;
-  }
-
-  if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
-  {
-    OperationProgress->AddSkippedFileSize(File->Size);
-    THROW_SKIP_FILE_NULL;
-  }
-
-  FTerminal->LogFileDetails(FileName, File->Modification, File->Size);
+  ResetFileTransfer();
 
-  OperationProgress->SetFile(FileName);
+  TFileTransferData UserData;
 
-  UnicodeString DestFileName =
-    FTerminal->ChangeFileName(
-      CopyParam, OnlyFileName, osRemote, FLAGSET(Flags, tfFirstLevel));
   UnicodeString DestFullName = TargetDir + DestFileName;
+  UnicodeString FilePath = UnixExtractFilePath(FileName);
+  unsigned int TransferType = (OperationProgress->AsciiTransfer ? 1 : 2);
 
-  if (File->IsDirectory)
-  {
-    Action.Cancel();
-    if (FTerminal->CanRecurseToDirectory(File))
-    {
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        int Attrs = FileGetAttrFix(ApiPath(DestFullName));
-        if (FLAGCLEAR(Attrs, faDirectory))
-        {
-          EXCEPTION;
-        }
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(NOT_DIRECTORY_ERROR, (DestFullName)));
-
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        THROWOSIFFALSE(ForceDirectories(ApiPath(DestFullName)));
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_DIR_ERROR, (DestFullName)));
-
-      if (FLAGCLEAR(Params, cpNoRecurse))
-      {
-        TSinkFileParams SinkFileParams;
-        SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName);
-        SinkFileParams.CopyParam = CopyParam;
-        SinkFileParams.Params = Params;
-        SinkFileParams.OperationProgress = OperationProgress;
-        SinkFileParams.Skipped = false;
-        SinkFileParams.Flags = Flags & ~(tfFirstLevel | tfAutoResume);
-
-        FTerminal->ProcessDirectory(FileName, SinkFile, &SinkFileParams);
-
-        // Do not delete directory if some of its files were skip.
-        // Throw "skip file" for the directory to avoid attempt to deletion
-        // of any parent directory
-        if (FLAGSET(Params, cpDelete) && SinkFileParams.Skipped)
-        {
-          THROW_SKIP_FILE_NULL;
-        }
-      }
-    }
-    else
-    {
-      FTerminal->LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
-    }
-  }
-  else
   {
-    AutoDetectTimeDifference(UnixExtractFileDir(FileName), CopyParam, Params);
-
-    FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
-
-    // Will we use ASCII of BINARY file transfer?
-    OperationProgress->SetAsciiTransfer(
-      CopyParam->UseAsciiTransfer(BaseFileName, osRemote, MaskParams));
-    FTerminal->LogEvent(UnicodeString((OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary")) +
-      L" transfer mode selected.");
-
-    // Suppose same data size to transfer as to write
-    OperationProgress->SetTransferSize(File->Size);
-    OperationProgress->SetLocalSize(OperationProgress->TransferSize);
-
-    int Attrs;
-    FILE_OPERATION_LOOP_BEGIN
-    {
-      Attrs = FileGetAttrFix(ApiPath(DestFullName));
-      if ((Attrs >= 0) && FLAGSET(Attrs, faDirectory))
-      {
-        EXCEPTION;
-      }
-    }
-    FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
-
-    ResetFileTransfer();
-
-    TFileTransferData UserData;
-
-    UnicodeString FilePath = UnixExtractFilePath(FileName);
-    unsigned int TransferType = (OperationProgress->AsciiTransfer ? 1 : 2);
-
-    {
-      // ignore file list
-      TFileListHelper Helper(this, NULL, true);
-
-      SetCPSLimit(OperationProgress);
-      FFileTransferPreserveTime = CopyParam->PreserveTime;
-      // not used for downloads anyway
-      FFileTransferRemoveBOM = CopyParam->RemoveBOM;
-      FFileTransferNoList = CanTransferSkipList(Params, Flags, CopyParam);
-      UserData.FileName = DestFileName;
-      UserData.Params = Params;
-      UserData.AutoResume = FLAGSET(Flags, tfAutoResume);
-      UserData.CopyParam = CopyParam;
-      UserData.Modification = File->Modification;
-      FileTransfer(FileName, DestFullName, OnlyFileName,
-        FilePath, true, File->Size, TransferType, UserData, OperationProgress);
-    }
-
-    // in case dest filename is changed from overwrite dialog
-    if (DestFileName != UserData.FileName)
-    {
-      DestFullName = TargetDir + UserData.FileName;
-      Attrs = FileGetAttrFix(ApiPath(DestFullName));
-    }
-
-    UnicodeString ExpandedDestFullName = ExpandUNCFileName(DestFullName);
-    Action.Destination(ExpandedDestFullName);
-
-    if (Attrs == -1)
-    {
-      Attrs = faArchive;
-    }
-    int NewAttrs = CopyParam->LocalFileAttrs(*File->Rights);
-    if ((NewAttrs & Attrs) != NewAttrs)
-    {
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        THROWOSIFFALSE(FileSetAttr(ApiPath(DestFullName), Attrs | NewAttrs) == 0);
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DestFullName)));
-    }
+    // ignore file list
+    TFileListHelper Helper(this, NULL, true);
 
-    FTerminal->LogFileDone(OperationProgress, ExpandedDestFullName);
+    SetCPSLimit(OperationProgress);
+    FFileTransferPreserveTime = CopyParam->PreserveTime;
+    // not used for downloads anyway
+    FFileTransferRemoveBOM = CopyParam->RemoveBOM;
+    FFileTransferNoList = CanTransferSkipList(Params, Flags, CopyParam);
+    UserData.FileName = DestFileName;
+    UserData.Params = Params;
+    UserData.AutoResume = FLAGSET(Flags, tfAutoResume);
+    UserData.CopyParam = CopyParam;
+    UserData.Modification = File->Modification;
+    UnicodeString OnlyFileName = UnixExtractFileName(FileName);
+    FileTransfer(FileName, DestFullName, OnlyFileName,
+      FilePath, true, File->Size, TransferType, UserData, OperationProgress);
   }
 
-  if (FLAGSET(Params, cpDelete))
+  // in case dest filename is changed from overwrite dialog
+  if (DestFileName != UserData.FileName)
   {
-    DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
-    // If file is directory, do not delete it recursively, because it should be
-    // empty already. If not, it should not be deleted (some files were
-    // skipped or some new files were copied to it, while we were downloading)
-    int Params = dfNoRecursive;
-    FTerminal->DeleteFile(FileName, File, &Params);
+    DestFullName = TargetDir + UserData.FileName;
+    Attrs = FileGetAttrFix(ApiPath(DestFullName));
   }
-}
-//---------------------------------------------------------------------------
-void __fastcall TFTPFileSystem::SinkFile(UnicodeString FileName,
-  const TRemoteFile * File, void * Param)
-{
-  TSinkFileParams * Params = (TSinkFileParams *)Param;
-  DebugAssert(Params->OperationProgress);
-  try
-  {
-    SinkRobust(FileName, File, Params->TargetDir, Params->CopyParam,
-      Params->Params, Params->OperationProgress, Params->Flags);
-  }
-  catch(EScpSkipFile & E)
-  {
-    TFileOperationProgressType * OperationProgress = Params->OperationProgress;
 
-    Params->Skipped = true;
+  UnicodeString ExpandedDestFullName = ExpandUNCFileName(DestFullName);
+  Action.Destination(ExpandedDestFullName);
 
-    {
-      TSuspendFileOperationProgress Suspend(OperationProgress);
-      if (!FTerminal->HandleException(&E))
-      {
-        throw;
-      }
-    }
+  FTerminal->UpdateTargetAttrs(DestFullName, File, CopyParam, Attrs);
 
-    if (OperationProgress->Cancel)
-    {
-      Abort();
-    }
-  }
+  FLastDataSent = Now();
 }
 //---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::TransferOnDirectory(
@@ -4588,8 +4351,7 @@ bool __fastcall TFTPFileSystem::Unquote(UnicodeString & Str)
 void __fastcall TFTPFileSystem::PreserveDownloadFileTime(HANDLE Handle, void * UserData)
 {
   TFileTransferData * Data = static_cast<TFileTransferData *>(UserData);
-  FILETIME WrTime = DateTimeToFileTime(Data->Modification, dstmUnix);
-  SetFileTime(Handle, NULL, NULL, &WrTime);
+  FTerminal->UpdateTargetTime(Handle, Data->Modification, dstmUnix);
 }
 //---------------------------------------------------------------------------
 bool __fastcall TFTPFileSystem::GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time)

+ 5 - 5
source/core/FtpFileSystem.h

@@ -58,6 +58,11 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action);
   virtual void __fastcall CreateDirectory(const UnicodeString DirName);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
@@ -157,11 +162,6 @@ protected:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TDownloadSessionAction & Action);
-  void __fastcall SinkRobust(const UnicodeString FileName,
-    const TRemoteFile * File, const UnicodeString TargetDir,
-    const TCopyParamType * CopyParam, int Params,
-    TFileOperationProgressType * OperationProgress, unsigned int Flags);
-  void __fastcall SinkFile(UnicodeString FileName, const TRemoteFile * File, void * Param);
   bool __fastcall ConfirmOverwrite(const UnicodeString & SourceFullFileName, UnicodeString & TargetFileName,
     TOverwriteMode & OverwriteMode, TFileOperationProgressType * OperationProgress,
     const TOverwriteFileParams * FileParams, const TCopyParamType * CopyParam,

+ 9 - 0
source/core/S3FileSystem.cpp

@@ -1435,6 +1435,15 @@ void __fastcall TS3FileSystem::CopyToLocal(TStrings * /*FilesToCopy*/,
   throw Exception(L"Not implemented");
 }
 //---------------------------------------------------------------------------
+void __fastcall TS3FileSystem::Sink(
+  const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/,
+  const UnicodeString & /*TargetDir*/, UnicodeString & /*DestFileName*/, int /*Attrs*/,
+  const TCopyParamType * /*CopyParam*/, int /*Params*/, TFileOperationProgressType * /*OperationProgress*/,
+  unsigned int /*Flags*/, TDownloadSessionAction & /*Action*/)
+{
+  throw Exception(L"Not implemented");
+}
+//---------------------------------------------------------------------------
 void __fastcall TS3FileSystem::GetSupportedChecksumAlgs(TStrings * /*Algs*/)
 {
   // NOOP

+ 5 - 0
source/core/S3FileSystem.h

@@ -65,6 +65,11 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action);
   virtual void __fastcall CreateDirectory(const UnicodeString DirName);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,

+ 15 - 13
source/core/ScpFileSystem.cpp

@@ -2218,6 +2218,15 @@ void __fastcall TSCPFileSystem::CopyToLocal(TStrings * FilesToCopy,
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TSCPFileSystem::Sink(
+  const UnicodeString & /*FileName*/, const TRemoteFile * /*File*/,
+  const UnicodeString & /*TargetDir*/, UnicodeString & /*DestFileName*/, int /*Attrs*/,
+  const TCopyParamType * /*CopyParam*/, int /*Params*/, TFileOperationProgressType * /*OperationProgress*/,
+  unsigned int /*Flags*/, TDownloadSessionAction & /*Action*/)
+{
+  DebugFail();
+}
+//---------------------------------------------------------------------------
 void __fastcall TSCPFileSystem::SCPError(const UnicodeString Message, bool Fatal)
 {
   SCPSendError(Message, Fatal);
@@ -2244,13 +2253,11 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
   struct
   {
     int SetTime;
-    FILETIME AcTime;
-    FILETIME WrTime;
+    TDateTime Modification;
     TRights RemoteRights;
     int Attrs;
     bool Exists;
   } FileData;
-  TDateTime SourceTimestamp;
 
   bool SkipConfirmed = false;
   bool Initialized = (Level > 0);
@@ -2335,12 +2342,7 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
             unsigned long MTime, ATime;
             if (swscanf(Line.c_str(), L"%ld %*d %ld %*d",  &MTime, &ATime) == 2)
             {
-              FileData.AcTime = DateTimeToFileTime(UnixToDateTime(ATime,
-                FTerminal->SessionData->DSTMode), FTerminal->SessionData->DSTMode);
-              FileData.WrTime = DateTimeToFileTime(UnixToDateTime(MTime,
-                FTerminal->SessionData->DSTMode), FTerminal->SessionData->DSTMode);
-              SourceTimestamp = UnixToDateTime(MTime,
-                FTerminal->SessionData->DSTMode);
+              FileData.Modification = UnixToDateTime(MTime, FTerminal->SessionData->DSTMode);
               FSecureShell->SendNull();
               // File time is only valid until next pass
               FileData.SetTime = 2;
@@ -2360,7 +2362,7 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
         }
 
         TFileMasks::TParams MaskParams;
-        MaskParams.Modification = SourceTimestamp;
+        MaskParams.Modification = FileData.Modification;
 
         // We reach this point only if control record was 'C' or 'D'
         try
@@ -2413,7 +2415,7 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
           OperationProgress->AddSkippedFileSize(MaskParams.Size);
         }
 
-        FTerminal->LogFileDetails(FileName, SourceTimestamp, MaskParams.Size);
+        FTerminal->LogFileDetails(FileName, FileData.Modification, MaskParams.Size);
 
         UnicodeString DestFileNameOnly =
           FTerminal->ChangeFileName(
@@ -2467,7 +2469,7 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
                   __int64 MTime;
                   TOverwriteFileParams FileParams;
                   FileParams.SourceSize = OperationProgress->TransferSize;
-                  FileParams.SourceTimestamp = SourceTimestamp;
+                  FileParams.SourceTimestamp = FileData.Modification;
                   FTerminal->OpenLocalFile(DestFileName, GENERIC_READ,
                     NULL, NULL, NULL, &MTime, NULL,
                     &FileParams.DestSize);
@@ -2591,7 +2593,7 @@ void __fastcall TSCPFileSystem::SCPSink(const UnicodeString TargetDir,
 
               if (FileData.SetTime && CopyParam->PreserveTime)
               {
-                SetFileTime(File, NULL, &FileData.AcTime, &FileData.WrTime);
+                FTerminal->UpdateTargetTime(File, FileData.Modification, FTerminal->SessionData->DSTMode);
               }
             }
             __finally

+ 5 - 0
source/core/ScpFileSystem.h

@@ -46,6 +46,11 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action);
   virtual void __fastcall CreateDirectory(const UnicodeString DirName);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,

+ 296 - 580
source/core/SftpFileSystem.cpp

@@ -1844,16 +1844,6 @@ struct TOpenRemoteFileParams
   TOverwriteFileParams * FileParams;
   bool Confirmed;
 };
-//---------------------------------------------------------------------------
-struct TSinkFileParams
-{
-  UnicodeString TargetDir;
-  const TCopyParamType * CopyParam;
-  int Params;
-  TFileOperationProgressType * OperationProgress;
-  bool Skipped;
-  unsigned int Flags;
-};
 //===========================================================================
 __fastcall TSFTPFileSystem::TSFTPFileSystem(TTerminal * ATerminal,
   TSecureShell * SecureShell):
@@ -5110,705 +5100,431 @@ void __fastcall TSFTPFileSystem::CopyToLocal(TStrings * FilesToCopy,
   int Params, TFileOperationProgressType * OperationProgress,
   TOnceDoneOperation & OnceDoneOperation)
 {
-  DebugAssert(FilesToCopy && OperationProgress);
-
-  UnicodeString FileName;
-  UnicodeString FullTargetDir = IncludeTrailingBackslash(TargetDir);
-  const TRemoteFile * File;
-  bool Success;
-  int Index = 0;
-  while (Index < FilesToCopy->Count && !OperationProgress->Cancel)
-  {
-    Success = false;
-    FileName = FilesToCopy->Strings[Index];
-    File = (TRemoteFile *)FilesToCopy->Objects[Index];
-
-    DebugAssert(!FAvoidBusy);
-    FAvoidBusy = true;
-
-    try
-    {
-      try
-      {
-        SFTPSinkRobust(LocalCanonify(FileName), File, FullTargetDir, CopyParam,
-          Params, OperationProgress, tfFirstLevel);
-        Success = true;
-      }
-      catch(EScpSkipFile & E)
-      {
-        TSuspendFileOperationProgress Suspend(OperationProgress);
-        if (!FTerminal->HandleException(&E))
-        {
-          throw;
-        }
-      }
-      catch(...)
-      {
-        // TODO: remove the block?
-        throw;
-      }
-    }
-    __finally
-    {
-      FAvoidBusy = false;
-      OperationProgress->Finish(FileName, Success, OnceDoneOperation);
-    }
-    Index++;
-  }
+  TAutoFlag AvoidBusyFlag(FAvoidBusy);
+  FTerminal->DoCopyToLocal(FilesToCopy, TargetDir, CopyParam, Params, OperationProgress, tfNone, OnceDoneOperation);
 }
 //---------------------------------------------------------------------------
-void __fastcall TSFTPFileSystem::SFTPSinkRobust(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags)
+void __fastcall TSFTPFileSystem::DirectorySunk(
+  const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam)
 {
-  // the same in TFTPFileSystem
-
-  TDownloadSessionAction Action(FTerminal->ActionLog);
-  TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
-
-  do
+  if (CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
   {
-    bool ChildError = false;
-    try
-    {
-      SFTPSink(FileName, File, TargetDir, CopyParam, Params, OperationProgress,
-        Flags, Action, ChildError);
-    }
-    catch(Exception & E)
+    // FILE_FLAG_BACKUP_SEMANTICS is needed to "open" directory
+    HANDLE LocalHandle =
+      CreateFile(
+        ApiPath(DestFullName).c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+        FILE_FLAG_BACKUP_SEMANTICS, 0);
+
+    if (LocalHandle == INVALID_HANDLE_VALUE)
     {
-      if (!RobustLoop.TryReopen(E))
-      {
-        if (!ChildError)
-        {
-          FTerminal->RollbackAction(Action, OperationProgress, &E);
-        }
-        throw;
-      }
+      int SetFileTimeError = GetLastError();
+      FTerminal->LogEvent(
+        FORMAT(L"Preserving directory timestamp failed, ignoring: %s", (SysErrorMessageForError(SetFileTimeError))));
     }
-
-    if (RobustLoop.ShouldRetry())
+    else
     {
-      OperationProgress->RollbackTransfer();
-      Action.Restart();
-      DebugAssert(File != NULL);
-      if (!File->IsDirectory)
-      {
-        // prevent overwrite and resume confirmations
-        Params |= cpNoConfirmation;
-      }
+      FTerminal->UpdateTargetTime(LocalHandle, File->Modification, FTerminal->SessionData->DSTMode);
+      CloseHandle(LocalHandle);
     }
   }
-  while (RobustLoop.Retry());
 }
 //---------------------------------------------------------------------------
-void __fastcall TSFTPFileSystem::SFTPSink(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags,
-  TDownloadSessionAction & Action, bool & ChildError)
+void __fastcall TSFTPFileSystem::Sink(
+  const UnicodeString & FileName, const TRemoteFile * File,
+  const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+  const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+  unsigned int /*Flags*/, TDownloadSessionAction & Action)
 {
-
-  Action.FileName(FileName);
-
-  UnicodeString OnlyFileName = UnixExtractFileName(FileName);
-
-  TFileMasks::TParams MaskParams;
-  DebugAssert(File);
-  MaskParams.Size = File->Size;
-  MaskParams.Modification = File->Modification;
-
-  UnicodeString BaseFileName = FTerminal->GetBaseFileName(FileName);
-  if (!CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams))
-  {
-    FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
-    THROW_SKIP_FILE_NULL;
-  }
-
-  if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
-  {
-    OperationProgress->AddSkippedFileSize(File->Size);
-    THROW_SKIP_FILE_NULL;
-  }
-
-  FTerminal->LogFileDetails(FileName, File->Modification, File->Size);
-
-  OperationProgress->SetFile(FileName);
-
-  UnicodeString DestFileName =
-    FTerminal->ChangeFileName(
-      CopyParam, OnlyFileName, osRemote, FLAGSET(Flags, tfFirstLevel));
+  // resume has no sense for temporary downloads
+  bool ResumeAllowed =
+    FLAGCLEAR(Params, cpTemporary) &&
+    !OperationProgress->AsciiTransfer &&
+    CopyParam->AllowResume(OperationProgress->TransferSize);
+
+  HANDLE LocalHandle = NULL;
+  TStream * FileStream = NULL;
+  bool DeleteLocalFile = false;
+  RawByteString RemoteHandle;
   UnicodeString DestFullName = TargetDir + DestFileName;
+  UnicodeString LocalFileName = DestFullName;
+  TSFTPOverwriteMode OverwriteMode = omOverwrite;
 
-  if (File->IsDirectory)
+  try
   {
-    Action.Cancel();
-    if (FTerminal->CanRecurseToDirectory(File))
-    {
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        int Attrs = FileGetAttrFix(ApiPath(DestFullName));
-        if ((Attrs & faDirectory) == 0) EXCEPTION;
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(NOT_DIRECTORY_ERROR, (DestFullName)));
+    bool ResumeTransfer = false;
+    UnicodeString DestPartialFullName;
 
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        THROWOSIFFALSE(ForceDirectories(ApiPath(DestFullName)));
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_DIR_ERROR, (DestFullName)));
+    if (ResumeAllowed)
+    {
+      DestPartialFullName = DestFullName + FTerminal->Configuration->PartialExt;
+      LocalFileName = DestPartialFullName;
 
-      if (FLAGCLEAR(Params, cpNoRecurse))
+      FTerminal->LogEvent(L"Checking existence of partially transferred file.");
+      if (FileExists(ApiPath(DestPartialFullName)))
       {
-        TSinkFileParams SinkFileParams;
-        SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName);
-        SinkFileParams.CopyParam = CopyParam;
-        SinkFileParams.Params = Params;
-        SinkFileParams.OperationProgress = OperationProgress;
-        SinkFileParams.Skipped = false;
-        SinkFileParams.Flags = Flags & ~tfFirstLevel;
-
-        FTerminal->ProcessDirectory(FileName, SFTPSinkFile, &SinkFileParams);
+        FTerminal->LogEvent(L"Partially transferred file exists.");
+        __int64 ResumeOffset;
+        FTerminal->OpenLocalFile(DestPartialFullName, GENERIC_WRITE,
+          NULL, &LocalHandle, NULL, NULL, NULL, &ResumeOffset);
 
-        if (CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
+        bool PartialBiggerThanSource = (ResumeOffset > OperationProgress->TransferSize);
+        if (FLAGCLEAR(Params, cpNoConfirmation))
         {
-          FTerminal->LogEvent(FORMAT(L"Preserving directory timestamp [%s]",
-            (StandardTimestamp(File->Modification))));
-          int SetFileTimeError = ERROR_SUCCESS;
-          // FILE_FLAG_BACKUP_SEMANTICS is needed to "open" directory
-          HANDLE LocalHandle = CreateFile(ApiPath(DestFullName).c_str(), GENERIC_WRITE,
-            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
-          if (LocalHandle == INVALID_HANDLE_VALUE)
-          {
-            SetFileTimeError = GetLastError();
-          }
-          else
+          ResumeTransfer = SFTPConfirmResume(DestFileName, PartialBiggerThanSource, OperationProgress);
+        }
+        else
+        {
+          ResumeTransfer = !PartialBiggerThanSource;
+          if (!ResumeTransfer)
           {
-            FILETIME AcTime = DateTimeToFileTime(File->LastAccess, FTerminal->SessionData->DSTMode);
-            FILETIME WrTime = DateTimeToFileTime(File->Modification, FTerminal->SessionData->DSTMode);
-            if (!SetFileTime(LocalHandle, NULL, &AcTime, &WrTime))
-            {
-              SetFileTimeError = GetLastError();
-            }
-            CloseHandle(LocalHandle);
+            FTerminal->LogEvent(L"Partially transferred file is bigger that original file.");
           }
+        }
 
-          if (SetFileTimeError != ERROR_SUCCESS)
+        if (!ResumeTransfer)
+        {
+          CloseHandle(LocalHandle);
+          LocalHandle = NULL;
+          FILE_OPERATION_LOOP_BEGIN
           {
-            FTerminal->LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s",
-              (SysErrorMessageForError(SetFileTimeError))));
+            THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(DestPartialFullName)));
           }
+          FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (DestPartialFullName)));
         }
-
-        // Do not delete directory if some of its files were skip.
-        // Throw "skip file" for the directory to avoid attempt to deletion
-        // of any parent directory
-        if ((Params & cpDelete) && SinkFileParams.Skipped)
+        else
         {
-          THROW_SKIP_FILE_NULL;
+          FTerminal->LogEvent(L"Resuming file transfer.");
+          FileSeek((THandle)LocalHandle, ResumeOffset, 0);
+          OperationProgress->AddResumed(ResumeOffset);
         }
       }
+
+      OperationProgress->Progress();
     }
-    else
+
+    // first open source file, not to loose the destination file,
+    // if we cannot open the source one in the first place
+    FTerminal->LogEvent(L"Opening remote file.");
+    FILE_OPERATION_LOOP_BEGIN
     {
-      FTerminal->LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
+      int OpenType = SSH_FXF_READ;
+      if ((FVersion >= 4) && OperationProgress->AsciiTransfer)
+      {
+        OpenType |= SSH_FXF_TEXT;
+      }
+      RemoteHandle = SFTPOpenRemoteFile(FileName, OpenType);
+      OperationProgress->Progress();
     }
-  }
-  else
-  {
-    FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
-
-    UnicodeString DestPartialFullName;
-    bool ResumeAllowed;
-    bool ResumeTransfer = false;
-    __int64 ResumeOffset;
+    FILE_OPERATION_LOOP_END(FMTLOAD(SFTP_OPEN_FILE_ERROR, (FileName)));
 
-    // Will we use ASCII of BINARY file transfer?
-    OperationProgress->SetAsciiTransfer(
-      CopyParam->UseAsciiTransfer(BaseFileName, osRemote, MaskParams));
-    FTerminal->LogEvent(UnicodeString((OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary")) +
-      " transfer mode selected.");
+    TSFTPPacket RemoteFilePacket(SSH_FXP_FSTAT);
+    RemoteFilePacket.AddString(RemoteHandle);
+    SendCustomReadFile(&RemoteFilePacket, &RemoteFilePacket, SSH_FILEXFER_ATTR_MODIFYTIME);
+    ReceiveResponse(&RemoteFilePacket, &RemoteFilePacket);
+    OperationProgress->Progress();
 
-    // Suppose same data size to transfer as to write
-    // (not true with ASCII transfer)
-    OperationProgress->SetTransferSize(File->Size);
-    OperationProgress->SetLocalSize(OperationProgress->TransferSize);
-
-    // resume has no sense for temporary downloads
-    ResumeAllowed = ((Params & cpTemporary) == 0) &&
-      !OperationProgress->AsciiTransfer &&
-      CopyParam->AllowResume(OperationProgress->TransferSize);
-
-    int Attrs;
-    FILE_OPERATION_LOOP_BEGIN
+    TDateTime Modification = File->Modification; // fallback
+    // ignore errors
+    if (RemoteFilePacket.Type == SSH_FXP_ATTRS)
     {
-      Attrs = FileGetAttrFix(ApiPath(DestFullName));
-      if ((Attrs >= 0) && (Attrs & faDirectory)) EXCEPTION;
+      // load file, avoid completion (resolving symlinks) as we do not need that
+      std::unique_ptr<TRemoteFile> AFile(
+        LoadFile(&RemoteFilePacket, NULL, UnixExtractFileName(FileName), NULL, false));
+      if (AFile->Modification != TDateTime())
+      {
+        Modification = File->Modification;
+      }
     }
-    FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
 
-    HANDLE LocalHandle = NULL;
-    TStream * FileStream = NULL;
-    bool DeleteLocalFile = false;
-    RawByteString RemoteHandle;
-    UnicodeString LocalFileName = DestFullName;
-    TSFTPOverwriteMode OverwriteMode = omOverwrite;
-    UnicodeString ExpandedDestFullName;
-
-    try
+    if ((Attrs >= 0) && !ResumeTransfer)
     {
-      if (ResumeAllowed)
+      __int64 DestFileSize;
+      __int64 MTime;
+      FTerminal->OpenLocalFile(
+        DestFullName, GENERIC_WRITE, NULL, &LocalHandle, NULL, &MTime, NULL, &DestFileSize, false);
+
+      FTerminal->LogEvent(L"Confirming overwriting of file.");
+      TOverwriteFileParams FileParams;
+      FileParams.SourceSize = OperationProgress->TransferSize;
+      FileParams.SourceTimestamp = Modification;
+      FileParams.DestTimestamp = UnixToDateTime(MTime,
+        FTerminal->SessionData->DSTMode);
+      FileParams.DestSize = DestFileSize;
+      UnicodeString PrevDestFileName = DestFileName;
+      SFTPConfirmOverwrite(FileName, DestFileName, CopyParam, Params, OperationProgress, OverwriteMode, &FileParams);
+      if (PrevDestFileName != DestFileName)
       {
+        DestFullName = TargetDir + DestFileName;
         DestPartialFullName = DestFullName + FTerminal->Configuration->PartialExt;
-        LocalFileName = DestPartialFullName;
-
-        FTerminal->LogEvent(L"Checking existence of partially transferred file.");
-        if (FileExists(ApiPath(DestPartialFullName)))
+        if (ResumeAllowed)
         {
-          FTerminal->LogEvent(L"Partially transferred file exists.");
-          FTerminal->OpenLocalFile(DestPartialFullName, GENERIC_WRITE,
-            NULL, &LocalHandle, NULL, NULL, NULL, &ResumeOffset);
-
-          bool PartialBiggerThanSource = (ResumeOffset > OperationProgress->TransferSize);
-          if (FLAGCLEAR(Params, cpNoConfirmation))
+          if (FileExists(ApiPath(DestPartialFullName)))
           {
-            ResumeTransfer = SFTPConfirmResume(DestFileName,
-              PartialBiggerThanSource, OperationProgress);
-          }
-          else
-          {
-            ResumeTransfer = !PartialBiggerThanSource;
-            if (!ResumeTransfer)
-            {
-              FTerminal->LogEvent(L"Partially transferred file is bigger that original file.");
-            }
-          }
-
-          if (!ResumeTransfer)
-          {
-            CloseHandle(LocalHandle);
-            LocalHandle = NULL;
             FILE_OPERATION_LOOP_BEGIN
             {
               THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(DestPartialFullName)));
             }
             FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (DestPartialFullName)));
           }
-          else
-          {
-            FTerminal->LogEvent(L"Resuming file transfer.");
-            FileSeek((THandle)LocalHandle, ResumeOffset, 0);
-            OperationProgress->AddResumed(ResumeOffset);
-          }
+          LocalFileName = DestPartialFullName;
         }
-
-        OperationProgress->Progress();
-      }
-
-      // first open source file, not to loose the destination file,
-      // if we cannot open the source one in the first place
-      FTerminal->LogEvent(L"Opening remote file.");
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        int OpenType = SSH_FXF_READ;
-        if ((FVersion >= 4) && OperationProgress->AsciiTransfer)
+        else
         {
-          OpenType |= SSH_FXF_TEXT;
+          LocalFileName = DestFullName;
         }
-        RemoteHandle = SFTPOpenRemoteFile(FileName, OpenType);
-        OperationProgress->Progress();
       }
-      FILE_OPERATION_LOOP_END(FMTLOAD(SFTP_OPEN_FILE_ERROR, (FileName)));
-
-      TDateTime Modification;
-      FILETIME AcTime;
-      FILETIME WrTime;
 
-      TSFTPPacket RemoteFilePacket(SSH_FXP_FSTAT);
-      RemoteFilePacket.AddString(RemoteHandle);
-      SendCustomReadFile(&RemoteFilePacket, &RemoteFilePacket,
-        SSH_FILEXFER_ATTR_MODIFYTIME);
-      ReceiveResponse(&RemoteFilePacket, &RemoteFilePacket);
-      OperationProgress->Progress();
-
-      const TRemoteFile * AFile = NULL;
-      try
+      if (OverwriteMode == omOverwrite)
       {
-        // ignore errors
-        if (RemoteFilePacket.Type == SSH_FXP_ATTRS)
+        // is NULL when overwriting read-only file
+        if (LocalHandle)
         {
-          // load file, avoid completion (resolving symlinks) as we do not need that
-          AFile = LoadFile(&RemoteFilePacket, NULL, UnixExtractFileName(FileName),
-            NULL, false);
+          CloseHandle(LocalHandle);
+          LocalHandle = NULL;
         }
-
-        Modification =
-          (AFile != NULL) && (AFile->Modification != TDateTime()) ? AFile->Modification : File->Modification;
-        TDateTime LastAccess =
-          (AFile != NULL) && (AFile->LastAccess != TDateTime()) ? AFile->LastAccess : File->LastAccess;
-        AcTime = DateTimeToFileTime(LastAccess, FTerminal->SessionData->DSTMode);
-        WrTime = DateTimeToFileTime(Modification, FTerminal->SessionData->DSTMode);
       }
-      __finally
-      {
-        delete AFile;
-      }
-
-      if ((Attrs >= 0) && !ResumeTransfer)
+      else
       {
-        __int64 DestFileSize;
-        __int64 MTime;
-        FTerminal->OpenLocalFile(DestFullName, GENERIC_WRITE,
-          NULL, &LocalHandle, NULL, &MTime, NULL, &DestFileSize, false);
-
-        FTerminal->LogEvent(L"Confirming overwriting of file.");
-        TOverwriteFileParams FileParams;
-        FileParams.SourceSize = OperationProgress->TransferSize;
-        FileParams.SourceTimestamp = Modification;
-        FileParams.DestTimestamp = UnixToDateTime(MTime,
-          FTerminal->SessionData->DSTMode);
-        FileParams.DestSize = DestFileSize;
-        UnicodeString PrevDestFileName = DestFileName;
-        SFTPConfirmOverwrite(FileName, DestFileName, CopyParam, Params, OperationProgress, OverwriteMode, &FileParams);
-        if (PrevDestFileName != DestFileName)
+        // is NULL when overwriting read-only file, so following will
+        // probably fail anyway
+        if (LocalHandle == NULL)
         {
-          DestFullName = TargetDir + DestFileName;
-          DestPartialFullName = DestFullName + FTerminal->Configuration->PartialExt;
-          if (ResumeAllowed)
-          {
-            if (FileExists(ApiPath(DestPartialFullName)))
-            {
-              FILE_OPERATION_LOOP_BEGIN
-              {
-                THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(DestPartialFullName)));
-              }
-              FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (DestPartialFullName)));
-            }
-            LocalFileName = DestPartialFullName;
-          }
-          else
-          {
-            LocalFileName = DestFullName;
-          }
+          FTerminal->OpenLocalFile(DestFullName, GENERIC_WRITE, NULL, &LocalHandle, NULL, NULL, NULL, NULL);
         }
-
-        if (OverwriteMode == omOverwrite)
+        ResumeAllowed = false;
+        FileSeek((THandle)LocalHandle, DestFileSize, 0);
+        if (OverwriteMode == omAppend)
         {
-          // is NULL when overwriting read-only file
-          if (LocalHandle)
-          {
-            CloseHandle(LocalHandle);
-            LocalHandle = NULL;
-          }
+          FTerminal->LogEvent(L"Appending to file.");
         }
         else
         {
-          // is NULL when overwriting read-only file, so following will
-          // probably fail anyway
-          if (LocalHandle == NULL)
-          {
-            FTerminal->OpenLocalFile(DestFullName, GENERIC_WRITE,
-              NULL, &LocalHandle, NULL, NULL, NULL, NULL);
-          }
-          ResumeAllowed = false;
-          FileSeek((THandle)LocalHandle, DestFileSize, 0);
-          if (OverwriteMode == omAppend)
-          {
-            FTerminal->LogEvent(L"Appending to file.");
-          }
-          else
-          {
-            FTerminal->LogEvent(L"Resuming file transfer (append style).");
-            DebugAssert(OverwriteMode == omResume);
-            OperationProgress->AddResumed(DestFileSize);
-          }
+          FTerminal->LogEvent(L"Resuming file transfer (append style).");
+          DebugAssert(OverwriteMode == omResume);
+          OperationProgress->AddResumed(DestFileSize);
         }
       }
+    }
 
-      ExpandedDestFullName = ExpandUNCFileName(DestFullName);
-      Action.Destination(ExpandedDestFullName);
+    Action.Destination(ExpandUNCFileName(DestFullName));
 
-      // if not already opened (resume, append...), create new empty file
-      if (!LocalHandle)
+    // if not already opened (resume, append...), create new empty file
+    if (!LocalHandle)
+    {
+      if (!FTerminal->CreateLocalFile(LocalFileName, OperationProgress,
+             &LocalHandle, FLAGSET(Params, cpNoConfirmation)))
       {
-        if (!FTerminal->CreateLocalFile(LocalFileName, OperationProgress,
-               &LocalHandle, FLAGSET(Params, cpNoConfirmation)))
-        {
-          THROW_SKIP_FILE_NULL;
-        }
+        THROW_SKIP_FILE_NULL;
       }
-      DebugAssert(LocalHandle);
+    }
+    DebugAssert(LocalHandle);
 
-      DeleteLocalFile = true;
+    DeleteLocalFile = true;
 
-      FileStream = new TSafeHandleStream((THandle)LocalHandle);
+    FileStream = new TSafeHandleStream((THandle)LocalHandle);
 
-      // at end of this block queue is discarded
+    // at end of this block queue is discarded
+    {
+      TSFTPDownloadQueue Queue(this);
+      try
       {
-        TSFTPDownloadQueue Queue(this);
-        try
-        {
-          TSFTPPacket DataPacket;
+        TSFTPPacket DataPacket;
 
-          int QueueLen = int(File->Size / DownloadBlockSize(OperationProgress)) + 1;
-          if ((QueueLen > FTerminal->SessionData->SFTPDownloadQueue) ||
-              (QueueLen < 0))
+        int QueueLen = int(File->Size / DownloadBlockSize(OperationProgress)) + 1;
+        if ((QueueLen > FTerminal->SessionData->SFTPDownloadQueue) ||
+            (QueueLen < 0))
+        {
+          QueueLen = FTerminal->SessionData->SFTPDownloadQueue;
+        }
+        if (QueueLen < 1)
+        {
+          QueueLen = 1;
+        }
+        Queue.Init(QueueLen, RemoteHandle, OperationProgress->TransferredSize, OperationProgress);
+
+        bool Eof = false;
+        bool PrevIncomplete = false;
+        int GapFillCount = 0;
+        int GapCount = 0;
+        unsigned long Missing = 0;
+        unsigned long DataLen = 0;
+        unsigned long BlockSize;
+        bool ConvertToken = false;
+
+        while (!Eof)
+        {
+          if (Missing > 0)
           {
-            QueueLen = FTerminal->SessionData->SFTPDownloadQueue;
+            Queue.InitFillGapRequest(OperationProgress->TransferredSize, Missing, &DataPacket);
+            GapFillCount++;
+            SendPacketAndReceiveResponse(&DataPacket, &DataPacket, SSH_FXP_DATA, asEOF);
           }
-          if (QueueLen < 1)
+          else
           {
-            QueueLen = 1;
+            Queue.ReceivePacket(&DataPacket, BlockSize);
           }
-          Queue.Init(QueueLen, RemoteHandle, OperationProgress->TransferredSize,
-            OperationProgress);
-
-          bool Eof = false;
-          bool PrevIncomplete = false;
-          int GapFillCount = 0;
-          int GapCount = 0;
-          unsigned long Missing = 0;
-          unsigned long DataLen = 0;
-          unsigned long BlockSize;
-          bool ConvertToken = false;
-
-          while (!Eof)
+
+          if (DataPacket.Type == SSH_FXP_STATUS)
           {
-            if (Missing > 0)
-            {
-              Queue.InitFillGapRequest(OperationProgress->TransferredSize, Missing,
-                &DataPacket);
-              GapFillCount++;
-              SendPacketAndReceiveResponse(&DataPacket, &DataPacket,
-                SSH_FXP_DATA, asEOF);
-            }
-            else
-            {
-              Queue.ReceivePacket(&DataPacket, BlockSize);
-            }
+            // must be SSH_FX_EOF, any other status packet would raise exception
+            Eof = true;
+            // close file right away, before waiting for pending responses
+            SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress, true, true, NULL);
+            RemoteHandle = L""; // do not close file again in __finally block
+          }
 
-            if (DataPacket.Type == SSH_FXP_STATUS)
+          if (!Eof)
+          {
+            if ((Missing == 0) && PrevIncomplete)
             {
-              // must be SSH_FX_EOF, any other status packet would raise exception
-              Eof = true;
-              // close file right away, before waiting for pending responses
-              SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress,
-                true, true, NULL);
-              RemoteHandle = L""; // do not close file again in __finally block
+              // This can happen only if last request returned less bytes
+              // than expected, but exactly number of bytes missing to last
+              // known file size, but actually EOF was not reached.
+              // Can happen only when filesize has changed since directory
+              // listing and server returns less bytes than requested and
+              // file has some special file size.
+              FTerminal->LogEvent(FORMAT(
+                L"Received incomplete data packet before end of file, offset: %s, size: %d, requested: %d",
+                (IntToStr(OperationProgress->TransferredSize), int(DataLen), int(BlockSize))));
+              FTerminal->TerminalError(NULL, LoadStr(SFTP_INCOMPLETE_BEFORE_EOF));
             }
 
-            if (!Eof)
-            {
-              if ((Missing == 0) && PrevIncomplete)
-              {
-                // This can happen only if last request returned less bytes
-                // than expected, but exactly number of bytes missing to last
-                // known file size, but actually EOF was not reached.
-                // Can happen only when filesize has changed since directory
-                // listing and server returns less bytes than requested and
-                // file has some special file size.
-                FTerminal->LogEvent(FORMAT(
-                  L"Received incomplete data packet before end of file, "
-                   "offset: %s, size: %d, requested: %d",
-                  (IntToStr(OperationProgress->TransferredSize), int(DataLen),
-                  int(BlockSize))));
-                FTerminal->TerminalError(NULL, LoadStr(SFTP_INCOMPLETE_BEFORE_EOF));
-              }
-
-              // Buffer for one block of data
-              TFileBuffer BlockBuf;
+            // Buffer for one block of data
+            TFileBuffer BlockBuf;
 
-              DataLen = DataPacket.GetCardinal();
+            DataLen = DataPacket.GetCardinal();
 
-              PrevIncomplete = false;
-              if (Missing > 0)
-              {
-                DebugAssert(DataLen <= Missing);
-                Missing -= DataLen;
-              }
-              else if (DataLen < BlockSize)
+            PrevIncomplete = false;
+            if (Missing > 0)
+            {
+              DebugAssert(DataLen <= Missing);
+              Missing -= DataLen;
+            }
+            else if (DataLen < BlockSize)
+            {
+              if (OperationProgress->TransferredSize + DataLen !=
+                    OperationProgress->TransferSize)
               {
-                if (OperationProgress->TransferredSize + DataLen !=
-                      OperationProgress->TransferSize)
-                {
-                  // with native text transfer mode (SFTP>=4), do not bother about
-                  // getting less than requested, read offset is ignored anyway
-                  if ((FVersion < 4) || !OperationProgress->AsciiTransfer)
-                  {
-                    GapCount++;
-                    Missing = BlockSize - DataLen;
-                  }
-                }
-                else
+                // with native text transfer mode (SFTP>=4), do not bother about
+                // getting less than requested, read offset is ignored anyway
+                if ((FVersion < 4) || !OperationProgress->AsciiTransfer)
                 {
-                  PrevIncomplete = true;
+                  GapCount++;
+                  Missing = BlockSize - DataLen;
                 }
               }
-
-              DebugAssert(DataLen <= BlockSize);
-              BlockBuf.Insert(0, reinterpret_cast<const char *>(DataPacket.GetNextData(DataLen)), DataLen);
-              DataPacket.DataConsumed(DataLen);
-              OperationProgress->AddTransferred(DataLen);
-
-              if ((FVersion >= 6) && DataPacket.CanGetBool() && (Missing == 0))
+              else
               {
-                Eof = DataPacket.GetBool();
+                PrevIncomplete = true;
               }
+            }
 
-              if (OperationProgress->AsciiTransfer)
-              {
-                DebugAssert(!ResumeTransfer && !ResumeAllowed);
+            DebugAssert(DataLen <= BlockSize);
+            BlockBuf.Insert(0, reinterpret_cast<const char *>(DataPacket.GetNextData(DataLen)), DataLen);
+            DataPacket.DataConsumed(DataLen);
+            OperationProgress->AddTransferred(DataLen);
 
-                unsigned int PrevBlockSize = BlockBuf.Size;
-                BlockBuf.Convert(GetEOL(), FTerminal->Configuration->LocalEOLType, 0, ConvertToken);
-                OperationProgress->SetLocalSize(
-                  OperationProgress->LocalSize - PrevBlockSize + BlockBuf.Size);
-              }
+            if ((FVersion >= 6) && DataPacket.CanGetBool() && (Missing == 0))
+            {
+              Eof = DataPacket.GetBool();
+            }
 
-              FILE_OPERATION_LOOP_BEGIN
-              {
-                BlockBuf.WriteToStream(FileStream, BlockBuf.Size);
-              }
-              FILE_OPERATION_LOOP_END(FMTLOAD(WRITE_ERROR, (LocalFileName)));
+            if (OperationProgress->AsciiTransfer)
+            {
+              DebugAssert(!ResumeTransfer && !ResumeAllowed);
 
-              OperationProgress->AddLocallyUsed(BlockBuf.Size);
+              unsigned int PrevBlockSize = BlockBuf.Size;
+              BlockBuf.Convert(GetEOL(), FTerminal->Configuration->LocalEOLType, 0, ConvertToken);
+              OperationProgress->SetLocalSize(OperationProgress->LocalSize - PrevBlockSize + BlockBuf.Size);
             }
 
-            if (OperationProgress->Cancel != csContinue)
+            FILE_OPERATION_LOOP_BEGIN
             {
-              if (OperationProgress->ClearCancelFile())
-              {
-                THROW_SKIP_FILE_NULL;
-              }
-              else
-              {
-                Abort();
-              }
+              BlockBuf.WriteToStream(FileStream, BlockBuf.Size);
             }
-          };
+            FILE_OPERATION_LOOP_END(FMTLOAD(WRITE_ERROR, (LocalFileName)));
 
-          if (GapCount > 0)
-          {
-            FTerminal->LogEvent(FORMAT(
-              L"%d requests to fill %d data gaps were issued.",
-              (GapFillCount, GapCount)));
+            OperationProgress->AddLocallyUsed(BlockBuf.Size);
           }
-        }
-        __finally
-        {
-          Queue.DisposeSafe();
-        }
-        // queue is discarded here
-      }
 
-      if (CopyParam->PreserveTime)
-      {
-        FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]",
-          (StandardTimestamp(Modification))));
-        SetFileTime(LocalHandle, NULL, &AcTime, &WrTime);
-      }
-
-      CloseHandle(LocalHandle);
-      LocalHandle = NULL;
-
-      if (ResumeAllowed)
-      {
-        FILE_OPERATION_LOOP_BEGIN
-        {
-          if (FileExists(ApiPath(DestFullName)))
+          if (OperationProgress->Cancel != csContinue)
           {
-            DeleteFileChecked(DestFullName);
+            if (OperationProgress->ClearCancelFile())
+            {
+              THROW_SKIP_FILE_NULL;
+            }
+            else
+            {
+              Abort();
+            }
           }
-          THROWOSIFFALSE(Sysutils::RenameFile(DestPartialFullName, DestFullName));
-        }
-        FILE_OPERATION_LOOP_END(
-          FMTLOAD(RENAME_AFTER_RESUME_ERROR,
-            (ExtractFileName(DestPartialFullName), DestFileName)));
-      }
-
-      DeleteLocalFile = false;
+        };
 
-      if (Attrs == -1)
-      {
-        Attrs = faArchive;
-      }
-      int NewAttrs = CopyParam->LocalFileAttrs(*File->Rights);
-      if ((NewAttrs & Attrs) != NewAttrs)
-      {
-        FILE_OPERATION_LOOP_BEGIN
+        if (GapCount > 0)
         {
-          THROWOSIFFALSE(FileSetAttr(ApiPath(DestFullName), Attrs | NewAttrs) == 0);
+          FTerminal->LogEvent(FORMAT(L"%d requests to fill %d data gaps were issued.", (GapFillCount, GapCount)));
         }
-        FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DestFullName)));
       }
+      __finally
+      {
+        Queue.DisposeSafe();
+      }
+      // queue is discarded here
+    }
 
+    if (CopyParam->PreserveTime)
+    {
+      FTerminal->UpdateTargetTime(LocalHandle, Modification, FTerminal->SessionData->DSTMode);
     }
-    __finally
+
+    CloseHandle(LocalHandle);
+    LocalHandle = NULL;
+
+    if (ResumeAllowed)
     {
-      if (LocalHandle) CloseHandle(LocalHandle);
-      if (FileStream) delete FileStream;
-      if (DeleteLocalFile && (!ResumeAllowed || OperationProgress->LocallyUsed == 0) &&
-          (OverwriteMode == omOverwrite))
+      FILE_OPERATION_LOOP_BEGIN
       {
-        FILE_OPERATION_LOOP_BEGIN
+        if (FileExists(ApiPath(DestFullName)))
         {
-          THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(LocalFileName)));
+          DeleteFileChecked(DestFullName);
         }
-        FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (LocalFileName)));
-      }
-
-      // if the transfer was finished, the file is closed already
-      if (FTerminal->Active && !RemoteHandle.IsEmpty())
-      {
-        // do not wait for response
-        SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress,
-          true, true, NULL);
+        THROWOSIFFALSE(Sysutils::RenameFile(DestPartialFullName, DestFullName));
       }
+      FILE_OPERATION_LOOP_END(FMTLOAD(RENAME_AFTER_RESUME_ERROR, (ExtractFileName(DestPartialFullName), DestFileName)));
     }
 
-    FTerminal->LogFileDone(OperationProgress, ExpandedDestFullName);
-  }
+    DeleteLocalFile = false;
+
+    FTerminal->UpdateTargetAttrs(DestFullName, File, CopyParam, Attrs);
 
-  if (Params & cpDelete)
-  {
-    DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
-    ChildError = true;
-    // If file is directory, do not delete it recursively, because it should be
-    // empty already. If not, it should not be deleted (some files were
-    // skipped or some new files were copied to it, while we were downloading)
-    int Params = dfNoRecursive;
-    FTerminal->DeleteFile(FileName, File, &Params);
-    ChildError = false;
-  }
-}
-//---------------------------------------------------------------------------
-void __fastcall TSFTPFileSystem::SFTPSinkFile(UnicodeString FileName,
-  const TRemoteFile * File, void * Param)
-{
-  TSinkFileParams * Params = (TSinkFileParams *)Param;
-  DebugAssert(Params->OperationProgress);
-  try
-  {
-    SFTPSinkRobust(FileName, File, Params->TargetDir, Params->CopyParam,
-      Params->Params, Params->OperationProgress, Params->Flags);
   }
-  catch(EScpSkipFile & E)
+  __finally
   {
-    TFileOperationProgressType * OperationProgress = Params->OperationProgress;
+    if (LocalHandle)
+    {
+      CloseHandle(LocalHandle);
+    }
 
-    Params->Skipped = true;
+    if (FileStream != NULL)
+    {
+      delete FileStream;
+    }
 
+    if (DeleteLocalFile && (!ResumeAllowed || OperationProgress->LocallyUsed == 0) &&
+        (OverwriteMode == omOverwrite))
     {
-      TSuspendFileOperationProgress Suspend(OperationProgress);
-      if (!FTerminal->HandleException(&E))
+      FILE_OPERATION_LOOP_BEGIN
       {
-        throw;
+        THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(LocalFileName)));
       }
+      FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (LocalFileName)));
     }
 
-    if (OperationProgress->Cancel)
+    // if the transfer was finished, the file is closed already
+    if (FTerminal->Active && !RemoteHandle.IsEmpty())
     {
-      Abort();
+      // do not wait for response
+      SFTPCloseRemote(RemoteHandle, DestFileName, OperationProgress, true, true, NULL);
     }
   }
 }

+ 7 - 11
source/core/SftpFileSystem.h

@@ -57,6 +57,13 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
+  virtual void __fastcall DirectorySunk(
+    const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam);
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action);
   virtual void __fastcall CreateDirectory(const UnicodeString DirName);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
@@ -175,17 +182,6 @@ protected:
     TSFTPOverwriteMode & Mode, const TOverwriteFileParams * FileParams);
   bool SFTPConfirmResume(const UnicodeString DestFileName, bool PartialBiggerThanSource,
     TFileOperationProgressType * OperationProgress);
-  void __fastcall SFTPSinkRobust(const UnicodeString FileName,
-    const TRemoteFile * File, const UnicodeString TargetDir,
-    const TCopyParamType * CopyParam, int Params,
-    TFileOperationProgressType * OperationProgress, unsigned int Flags);
-  void __fastcall SFTPSink(const UnicodeString FileName,
-    const TRemoteFile * File, const UnicodeString TargetDir,
-    const TCopyParamType * CopyParam, int Params,
-    TFileOperationProgressType * OperationProgress, unsigned int Flags,
-    TDownloadSessionAction & Action, bool & ChildError);
-  void __fastcall SFTPSinkFile(UnicodeString FileName,
-    const TRemoteFile * File, void * Param);
   char * __fastcall GetEOL() const;
   inline void __fastcall BusyStart();
   inline void __fastcall BusyEnd();

+ 283 - 3
source/core/Terminal.cpp

@@ -6815,6 +6815,15 @@ void __fastcall TTerminal::DirectorySource(
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TTerminal::SelectTransferMode(
+  const UnicodeString & BaseFileName, TOperationSide Side, const TCopyParamType * CopyParam,
+  const TFileMasks::TParams & MaskParams)
+{
+  OperationProgress->SetAsciiTransfer(CopyParam->UseAsciiTransfer(BaseFileName, Side, MaskParams));
+  UnicodeString ModeName = (OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary");
+  LogEvent(FORMAT(L"%s transfer mode selected.", (ModeName)));
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminal::SelectSourceTransferMode(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam)
 {
   // Will we use ASCII or BINARY file transfer?
@@ -6822,9 +6831,7 @@ void __fastcall TTerminal::SelectSourceTransferMode(const TLocalFileHandle & Han
   MaskParams.Size = Handle.Size;
   MaskParams.Modification = Handle.Modification;
   UnicodeString BaseFileName = GetBaseFileName(Handle.FileName);
-  OperationProgress->SetAsciiTransfer(CopyParam->UseAsciiTransfer(BaseFileName, osLocal, MaskParams));
-  UnicodeString ModeName = (OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary");
-  LogEvent(FORMAT(L"%s transfer mode selected.", (ModeName)));
+  SelectTransferMode(BaseFileName, osLocal, CopyParam, MaskParams);
 }
 //---------------------------------------------------------------------------
 void __fastcall TTerminal::UpdateSource(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam, int Params)
@@ -7047,6 +7054,279 @@ bool __fastcall TTerminal::CopyToLocal(TStrings * FilesToCopy,
   return Result;
 }
 //---------------------------------------------------------------------------
+void __fastcall TTerminal::DoCopyToLocal(
+  TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
+  TFileOperationProgressType * OperationProgress, unsigned int Flags, TOnceDoneOperation & OnceDoneOperation)
+{
+  DebugAssert((FilesToCopy != NULL) && (OperationProgress != NULL));
+
+  UnicodeString FullTargetDir = IncludeTrailingBackslash(TargetDir);
+  int Index = 0;
+  while ((Index < FilesToCopy->Count) && !OperationProgress->Cancel)
+  {
+    bool Success = false;
+    UnicodeString FileName = FilesToCopy->Strings[Index];
+
+    try
+    {
+      try
+      {
+        UnicodeString AbsoluteFileName = AbsolutePath(FileName, true);
+        const TRemoteFile * File = dynamic_cast<const TRemoteFile *>(FilesToCopy->Objects[Index]);
+        SinkRobust(AbsoluteFileName, File, FullTargetDir, CopyParam, Params, OperationProgress, Flags | tfFirstLevel);
+        Success = true;
+      }
+      catch (EScpSkipFile & E)
+      {
+        TSuspendFileOperationProgress Suspend(OperationProgress);
+        if (!HandleException(&E))
+        {
+          throw;
+        }
+      }
+    }
+    __finally
+    {
+      OperationProgress->Finish(FileName, Success, OnceDoneOperation);
+    }
+    Index++;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TTerminal::SinkRobust(
+  const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
+  const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags)
+{
+  TDownloadSessionAction Action(ActionLog);
+  bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
+  TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny);
+
+  do
+  {
+    bool ChildError = false;
+    try
+    {
+      Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress, Flags, Action, ChildError);
+    }
+    catch (Exception & E)
+    {
+      if (!RobustLoop.TryReopen(E))
+      {
+        if (!ChildError)
+        {
+          RollbackAction(Action, OperationProgress, &E);
+        }
+        throw;
+      }
+    }
+
+    if (RobustLoop.ShouldRetry())
+    {
+      OperationProgress->RollbackTransfer();
+      Action.Restart();
+      DebugAssert(File != NULL);
+      if (!File->IsDirectory)
+      {
+        // prevent overwrite and resume confirmations
+        Params |= cpNoConfirmation;
+        Flags |= tfAutoResume;
+      }
+    }
+  }
+  while (RobustLoop.Retry());
+}
+//---------------------------------------------------------------------------
+struct TSinkFileParams
+{
+  UnicodeString TargetDir;
+  const TCopyParamType * CopyParam;
+  int Params;
+  TFileOperationProgressType * OperationProgress;
+  bool Skipped;
+  unsigned int Flags;
+};
+//---------------------------------------------------------------------------
+void __fastcall TTerminal::Sink(
+  const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
+  const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags,
+  TDownloadSessionAction & Action, bool & ChildError)
+{
+  Action.FileName(FileName);
+
+  TFileMasks::TParams MaskParams;
+  DebugAssert(File);
+  MaskParams.Size = File->Size;
+  MaskParams.Modification = File->Modification;
+
+  UnicodeString BaseFileName = GetBaseFileName(FileName);
+  if (!CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams))
+  {
+    LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
+    THROW_SKIP_FILE_NULL;
+  }
+
+  if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
+  {
+    OperationProgress->AddSkippedFileSize(File->Size);
+    THROW_SKIP_FILE_NULL;
+  }
+
+  LogFileDetails(FileName, File->Modification, File->Size);
+
+  OperationProgress->SetFile(FileName);
+
+  UnicodeString OnlyFileName = UnixExtractFileName(FileName);
+  UnicodeString DestFileName = ChangeFileName(CopyParam, OnlyFileName, osRemote, FLAGSET(Flags, tfFirstLevel));
+  UnicodeString DestFullName = TargetDir + DestFileName;
+
+  if (File->IsDirectory)
+  {
+    Action.Cancel();
+    if (CanRecurseToDirectory(File))
+    {
+      FILE_OPERATION_LOOP_BEGIN
+      {
+        int Attrs = FileGetAttrFix(ApiPath(DestFullName));
+        if (FLAGCLEAR(Attrs, faDirectory))
+        {
+          EXCEPTION;
+        }
+      }
+      FILE_OPERATION_LOOP_END(FMTLOAD(NOT_DIRECTORY_ERROR, (DestFullName)));
+
+      FILE_OPERATION_LOOP_BEGIN
+      {
+        THROWOSIFFALSE(ForceDirectories(ApiPath(DestFullName)));
+      }
+      FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_DIR_ERROR, (DestFullName)));
+
+      if (FLAGCLEAR(Params, cpNoRecurse))
+      {
+        TSinkFileParams SinkFileParams;
+        SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName);
+        SinkFileParams.CopyParam = CopyParam;
+        SinkFileParams.Params = Params;
+        SinkFileParams.OperationProgress = OperationProgress;
+        SinkFileParams.Skipped = false;
+        SinkFileParams.Flags = Flags & ~(tfFirstLevel | tfAutoResume);
+
+        ProcessDirectory(FileName, SinkFile, &SinkFileParams);
+
+        FFileSystem->DirectorySunk(DestFullName, File, CopyParam);
+
+        // Do not delete directory if some of its files were skip.
+        // Throw "skip file" for the directory to avoid attempt to deletion
+        // of any parent directory
+        if (FLAGSET(Params, cpDelete) && SinkFileParams.Skipped)
+        {
+          THROW_SKIP_FILE_NULL;
+        }
+      }
+    }
+    else
+    {
+      LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
+    }
+  }
+  else
+  {
+    LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
+
+    // Will we use ASCII of BINARY file transfer?
+    SelectTransferMode(BaseFileName, osRemote, CopyParam, MaskParams);
+
+    // Suppose same data size to transfer as to write
+    // (not true with ASCII transfer)
+    OperationProgress->SetTransferSize(File->Size);
+    OperationProgress->SetLocalSize(OperationProgress->TransferSize);
+
+    int Attrs;
+    FILE_OPERATION_LOOP_BEGIN
+    {
+      Attrs = FileGetAttrFix(ApiPath(DestFullName));
+      if ((Attrs >= 0) && FLAGSET(Attrs, faDirectory))
+      {
+        EXCEPTION;
+      }
+    }
+    FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
+
+    FFileSystem->Sink(
+      FileName, File, TargetDir, DestFileName, Attrs, CopyParam, Params, OperationProgress, Flags, Action);
+
+    LogFileDone(OperationProgress, ExpandUNCFileName(DestFullName));
+  }
+
+  if (FLAGSET(Params, cpDelete))
+  {
+    DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
+    ChildError = true;
+    // If file is directory, do not delete it recursively, because it should be
+    // empty already. If not, it should not be deleted (some files were
+    // skipped or some new files were copied to it, while we were downloading)
+    int Params = dfNoRecursive;
+    DeleteFile(FileName, File, &Params);
+    ChildError = false;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TTerminal::UpdateTargetAttrs(
+  const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam, int Attrs)
+{
+  if (Attrs == -1)
+  {
+    Attrs = faArchive;
+  }
+  int NewAttrs = CopyParam->LocalFileAttrs(*File->Rights);
+  if ((NewAttrs & Attrs) != NewAttrs)
+  {
+    FILE_OPERATION_LOOP_BEGIN
+    {
+      THROWOSIFFALSE(FileSetAttr(ApiPath(DestFullName), Attrs | NewAttrs) == 0);
+    }
+    FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DestFullName)));
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TTerminal::UpdateTargetTime(HANDLE Handle, TDateTime Modification, TDSTMode DSTMode)
+{
+  LogEvent(FORMAT(L"Preserving timestamp [%s]", (StandardTimestamp(Modification))));
+  FILETIME WrTime = DateTimeToFileTime(Modification, DSTMode);
+  if (!SetFileTime(Handle, NULL, NULL, &WrTime))
+  {
+    int Error = GetLastError();
+    LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s", (SysErrorMessageForError(Error))));
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TTerminal::SinkFile(UnicodeString FileName, const TRemoteFile * File, void * Param)
+{
+  TSinkFileParams * Params = static_cast<TSinkFileParams *>(Param);
+  DebugAssert(Params->OperationProgress != NULL);
+  try
+  {
+    SinkRobust(FileName, File, Params->TargetDir, Params->CopyParam,
+      Params->Params, Params->OperationProgress, Params->Flags);
+  }
+  catch (EScpSkipFile & E)
+  {
+    Params->Skipped = true;
+
+    {
+      TSuspendFileOperationProgress Suspend(Params->OperationProgress);
+      if (!HandleException(&E))
+      {
+        throw;
+      }
+    }
+
+    if (Params->OperationProgress->Cancel)
+    {
+      Abort();
+    }
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminal::ReflectSettings()
 {
   DebugAssert(FLog != NULL);

+ 18 - 0
source/core/Terminal.h

@@ -115,6 +115,7 @@ const int ropNoReadDirectory = 0x02;
 const int boDisableNeverShowAgain = 0x01;
 const int bpMonospacedFont = 0x01;
 //---------------------------------------------------------------------------
+const int tfNone = 0x00;
 const int tfFirstLevel = 0x01;
 const int tfNewDirectory = 0x02;
 const int tfAutoResume = 0x04;
@@ -432,8 +433,25 @@ protected:
     const UnicodeString & DirectoryName, const UnicodeString & TargetDir, const UnicodeString & DestDirectoryName,
     int Attrs, const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags);
+  void __fastcall SelectTransferMode(
+    const UnicodeString & BaseFileName, TOperationSide Side, const TCopyParamType * CopyParam,
+    const TFileMasks::TParams & MaskParams);
   void __fastcall SelectSourceTransferMode(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam);
   void __fastcall UpdateSource(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam, int Params);
+  void __fastcall DoCopyToLocal(
+    TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
+    TFileOperationProgressType * OperationProgress, unsigned int Flags, TOnceDoneOperation & OnceDoneOperation);
+  void __fastcall SinkRobust(
+    const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags);
+  void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags,
+    TDownloadSessionAction & Action, bool & ChildError);
+  void __fastcall SinkFile(UnicodeString FileName, const TRemoteFile * File, void * Param);
+  void __fastcall UpdateTargetAttrs(
+    const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam, int Attrs);
+  void __fastcall UpdateTargetTime(HANDLE Handle, TDateTime Modification, TDSTMode DSTMode);
 
   __property TFileOperationProgressType * OperationProgress = { read=FOperationProgress };
 

+ 56 - 296
source/core/WebDAVFileSystem.cpp

@@ -38,16 +38,6 @@
 //---------------------------------------------------------------------------
 #define FILE_OPERATION_LOOP_TERMINAL FTerminal
 //---------------------------------------------------------------------------
-struct TSinkFileParams
-{
-  UnicodeString TargetDir;
-  const TCopyParamType * CopyParam;
-  int Params;
-  TFileOperationProgressType * OperationProgress;
-  bool Skipped;
-  unsigned int Flags;
-};
-//---------------------------------------------------------------------------
 #define SESSION_FS_KEY "filesystem"
 static const UnicodeString CONST_WEBDAV_PROTOCOL_BASE_NAME = L"WebDAV";
 static const int HttpUnauthorized = 401;
@@ -1378,82 +1368,8 @@ void __fastcall TWebDAVFileSystem::CopyToLocal(TStrings * FilesToCopy,
   TOnceDoneOperation & OnceDoneOperation)
 {
   Params &= ~cpAppend;
-  UnicodeString FullTargetDir = ::IncludeTrailingBackslash(TargetDir);
-
-  int Index = 0;
-  while (Index < FilesToCopy->Count && !OperationProgress->Cancel)
-  {
-    UnicodeString FileName = FilesToCopy->Strings[Index];
-    const TRemoteFile * File = dynamic_cast<const TRemoteFile *>(FilesToCopy->Objects[Index]);
-    bool Success = false;
-    try
-    {
-      try
-      {
-        SinkRobust(AbsolutePath(FileName, false), File, FullTargetDir, CopyParam, Params,
-          OperationProgress, tfFirstLevel);
-        Success = true;
-      }
-      catch (EScpSkipFile & E)
-      {
-        TSuspendFileOperationProgress Suspend(OperationProgress);
-        if (!FTerminal->HandleException(&E))
-        {
-          throw;
-        }
-      }
-    }
-    __finally
-    {
-      OperationProgress->Finish(FileName, Success, OnceDoneOperation);
-    }
-    Index++;
-  }
-}
-//---------------------------------------------------------------------------
-void __fastcall TWebDAVFileSystem::SinkRobust(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags)
-{
-  // the same in TSFTPFileSystem
-
-  TDownloadSessionAction Action(FTerminal->ActionLog);
-  TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
-
-  do
-  {
-    bool ChildError = false;
-    try
-    {
-      Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress,
-        Flags, Action, ChildError);
-    }
-    catch (Exception & E)
-    {
-      if (!RobustLoop.TryReopen(E))
-      {
-        if (!ChildError)
-        {
-          FTerminal->RollbackAction(Action, OperationProgress, &E);
-        }
-        throw;
-      }
-    }
 
-    if (RobustLoop.ShouldRetry())
-    {
-      OperationProgress->RollbackTransfer();
-      Action.Restart();
-      DebugAssert(File != NULL);
-      if (!File->IsDirectory)
-      {
-        // prevent overwrite confirmations
-        Params |= cpNoConfirmation;
-      }
-    }
-  }
-  while (RobustLoop.Retry());
+  FTerminal->DoCopyToLocal(FilesToCopy, TargetDir, CopyParam, Params, OperationProgress, tfNone, OnceDoneOperation);
 }
 //---------------------------------------------------------------------------
 void TWebDAVFileSystem::NeonCreateRequest(
@@ -1729,249 +1645,93 @@ int TWebDAVFileSystem::NeonBodyReader(void * UserData, const char * Buf, size_t
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TWebDAVFileSystem::Sink(const UnicodeString FileName,
-  const TRemoteFile * File, const UnicodeString TargetDir,
-  const TCopyParamType * CopyParam, int Params,
-  TFileOperationProgressType * OperationProgress, unsigned int Flags,
-  TDownloadSessionAction & Action, bool & ChildError)
+void __fastcall TWebDAVFileSystem::Sink(
+  const UnicodeString & FileName, const TRemoteFile * File,
+  const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+  const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+  unsigned int /*Flags*/, TDownloadSessionAction & Action)
 {
-  UnicodeString FileNameOnly = UnixExtractFileName(FileName);
-
-  Action.FileName(FileName);
+  UnicodeString DestFullName = TargetDir + DestFileName;
+  if (FileExists(ApiPath(DestFullName)))
+  {
+    __int64 Size;
+    __int64 MTime;
+    FTerminal->OpenLocalFile(DestFullName, GENERIC_READ, NULL, NULL, NULL, &MTime, NULL, &Size);
+    TOverwriteFileParams FileParams;
 
-  DebugAssert(File);
-  TFileMasks::TParams MaskParams;
-  MaskParams.Size = File->Size;
-  MaskParams.Modification = File->Modification;
+    FileParams.SourceSize = File->Size;
+    FileParams.SourceTimestamp = File->Modification;
+    FileParams.DestSize = Size;
+    FileParams.DestTimestamp = UnixToDateTime(MTime, FTerminal->SessionData->DSTMode);
 
-  UnicodeString BaseFileName = FTerminal->GetBaseFileName(FileName);
-  if (!CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams))
-  {
-    FTerminal->LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
-    THROW_SKIP_FILE_NULL;
+    ConfirmOverwrite(FileName, DestFileName, OperationProgress, &FileParams, CopyParam, Params);
   }
 
-  if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
+  UnicodeString FilePath = ::UnixExtractFilePath(FileName);
+  if (FilePath.IsEmpty())
   {
-    OperationProgress->AddSkippedFileSize(File->Size);
-    THROW_SKIP_FILE_NULL;
+    FilePath = L"/";
   }
 
-  FTerminal->LogFileDetails(FileName, TDateTime(), File->Size);
+  UnicodeString ExpandedDestFullName = ExpandUNCFileName(DestFullName);
+  Action.Destination(ExpandedDestFullName);
 
-  OperationProgress->SetFile(FileName);
-
-  UnicodeString DestFileName =
-    FTerminal->ChangeFileName(
-      CopyParam, FileNameOnly, osRemote, FLAGSET(Flags, tfFirstLevel));
-  UnicodeString DestFullName = TargetDir + DestFileName;
-
-  if (File->IsDirectory)
-  {
-    Action.Cancel();
-    if (DebugAlwaysTrue(FTerminal->CanRecurseToDirectory(File)))
-    {
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        int Attrs = FileGetAttrFix(ApiPath(DestFullName));
-        if (FLAGCLEAR(Attrs, faDirectory)) { EXCEPTION; }
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(NOT_DIRECTORY_ERROR, (DestFullName)));
-
-      FILE_OPERATION_LOOP_BEGIN
-      {
-        THROWOSIFFALSE(ForceDirectories(ApiPath(DestFullName)));
-      }
-      FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_DIR_ERROR, (DestFullName)));
-
-      if (FLAGCLEAR(Params, cpNoRecurse))
-      {
-        TSinkFileParams SinkFileParams;
-        SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName);
-        SinkFileParams.CopyParam = CopyParam;
-        SinkFileParams.Params = Params;
-        SinkFileParams.OperationProgress = OperationProgress;
-        SinkFileParams.Skipped = false;
-        SinkFileParams.Flags = Flags & ~tfFirstLevel;
-
-        FTerminal->ProcessDirectory(FileName, SinkFile, &SinkFileParams);
-
-        // Do not delete directory if some of its files were skip.
-        // Throw "skip file" for the directory to avoid attempt to deletion
-        // of any parent directory
-        if (FLAGSET(Params, cpDelete) && SinkFileParams.Skipped)
-        {
-          THROW_SKIP_FILE_NULL;
-        }
-      }
-    }
-    else
-    {
-      // file is symlink to directory, currently do nothing, but it should be
-      // reported to user
-    }
-  }
-  else
+  FILE_OPERATION_LOOP_BEGIN
   {
-    FTerminal->LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
-    if (FileExists(ApiPath(DestFullName)))
+    HANDLE LocalHandle;
+    if (!FTerminal->CreateLocalFile(DestFullName, OperationProgress, &LocalHandle, FLAGSET(Params, cpNoConfirmation)))
     {
-      __int64 Size;
-      __int64 MTime;
-      FTerminal->OpenLocalFile(DestFullName, GENERIC_READ, NULL,
-        NULL, NULL, &MTime, NULL, &Size);
-      TOverwriteFileParams FileParams;
-
-      FileParams.SourceSize = File->Size;
-      FileParams.SourceTimestamp = File->Modification;
-      FileParams.DestSize = Size;
-      FileParams.DestTimestamp = UnixToDateTime(MTime,
-        FTerminal->SessionData->DSTMode);
-
-      ConfirmOverwrite(FileName, DestFileName, OperationProgress,
-        &FileParams, CopyParam, Params);
-    }
-
-    // Suppose same data size to transfer as to write
-    OperationProgress->SetTransferSize(File->Size);
-    OperationProgress->SetLocalSize(OperationProgress->TransferSize);
-
-    int Attrs = -1;
-    FILE_OPERATION_LOOP_BEGIN
-    {
-      Attrs = FileGetAttrFix(ApiPath(DestFullName));
-      if ((Attrs >= 0) && FLAGSET(Attrs, faDirectory)) { EXCEPTION; }
-    }
-    FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
-
-    UnicodeString FilePath = ::UnixExtractFilePath(FileName);
-    if (FilePath.IsEmpty())
-    {
-      FilePath = L"/";
+      THROW_SKIP_FILE_NULL;
     }
 
-    UnicodeString ExpandedDestFullName = ExpandUNCFileName(DestFullName);
-    Action.Destination(ExpandedDestFullName);
+    bool DeleteLocalFile = true;
 
-    FILE_OPERATION_LOOP_BEGIN
+    int FD = -1;
+    try
     {
-      HANDLE LocalHandle;
-      if (!FTerminal->CreateLocalFile(DestFullName, OperationProgress,
-             &LocalHandle, FLAGSET(Params, cpNoConfirmation)))
+      FD = _open_osfhandle((intptr_t)LocalHandle, O_BINARY);
+      if (FD < 0)
       {
         THROW_SKIP_FILE_NULL;
       }
 
-      bool DeleteLocalFile = true;
-
-      int FD = -1;
-      try
-      {
-        FD = _open_osfhandle((intptr_t)LocalHandle, O_BINARY);
-        if (FD < 0)
-        {
-          THROW_SKIP_FILE_NULL;
-        }
-
-        TAutoFlag DownloadingFlag(FDownloading);
+      TAutoFlag DownloadingFlag(FDownloading);
 
-        ClearNeonError();
-        CheckStatus(ne_get(FNeonSession, PathToNeon(FileName), FD));
-        DeleteLocalFile = false;
+      ClearNeonError();
+      CheckStatus(ne_get(FNeonSession, PathToNeon(FileName), FD));
+      DeleteLocalFile = false;
 
-        if (CopyParam->PreserveTime)
-        {
-          TDateTime Modification = File->Modification;
-          FILETIME WrTime = DateTimeToFileTime(Modification, FTerminal->SessionData->DSTMode);
-          FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]",
-            (StandardTimestamp(Modification))));
-          SetFileTime(LocalHandle, NULL, NULL, &WrTime);
-        }
-      }
-      __finally
+      if (CopyParam->PreserveTime)
       {
-        if (FD >= 0)
-        {
-          // _close calls CloseHandle internally (even doc states, we should not call CloseHandle),
-          // but it crashes code guard
-          _close(FD);
-        }
-        else
-        {
-          CloseHandle(LocalHandle);
-        }
-
-        if (DeleteLocalFile)
-        {
-          FILE_OPERATION_LOOP_BEGIN
-          {
-            THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(DestFullName)));
-          }
-          FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (DestFullName)));
-        }
+        FTerminal->UpdateTargetTime(LocalHandle, File->Modification, FTerminal->SessionData->DSTMode);
       }
     }
-    FILE_OPERATION_LOOP_END(FMTLOAD(TRANSFER_ERROR, (FileName)));
-
-    if (Attrs == -1)
-    {
-      Attrs = faArchive;
-    }
-    int NewAttrs = CopyParam->LocalFileAttrs(*File->Rights);
-    if ((NewAttrs & Attrs) != NewAttrs)
+    __finally
     {
-      FILE_OPERATION_LOOP_BEGIN
+      if (FD >= 0)
       {
-        THROWOSIFFALSE(FileSetAttr(ApiPath(DestFullName), Attrs | NewAttrs) == 0);
+        // _close calls CloseHandle internally (even doc states, we should not call CloseHandle),
+        // but it crashes code guard
+        _close(FD);
       }
-      FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DestFullName)));
-    }
-
-    FTerminal->LogFileDone(OperationProgress, ExpandedDestFullName);
-  }
-
-  if (FLAGSET(Params, cpDelete))
-  {
-    DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
-    ChildError = true;
-    // If file is directory, do not delete it recursively, because it should be
-    // empty already. If not, it should not be deleted (some files were
-    // skipped or some new files were copied to it, while we were downloading)
-    int Params = dfNoRecursive;
-    FTerminal->DeleteFile(FileName, File, &Params);
-    ChildError = false;
-  }
-}
-//---------------------------------------------------------------------------
-void __fastcall TWebDAVFileSystem::SinkFile(const UnicodeString FileName,
-  const TRemoteFile * File, void * Param)
-{
-  TSinkFileParams * Params = static_cast<TSinkFileParams *>(Param);
-  DebugAssert(Params->OperationProgress);
-  try
-  {
-    SinkRobust(FileName, File, Params->TargetDir, Params->CopyParam,
-      Params->Params, Params->OperationProgress, Params->Flags);
-  }
-  catch (EScpSkipFile & E)
-  {
-    TFileOperationProgressType * OperationProgress = Params->OperationProgress;
-
-    Params->Skipped = true;
-
-    {
-      TSuspendFileOperationProgress Suspend(OperationProgress);
-      if (!FTerminal->HandleException(&E))
+      else
       {
-        throw;
+        CloseHandle(LocalHandle);
       }
-    }
 
-    if (OperationProgress->Cancel)
-    {
-      Abort();
+      if (DeleteLocalFile)
+      {
+        FILE_OPERATION_LOOP_BEGIN
+        {
+          THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(DestFullName)));
+        }
+        FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (DestFullName)));
+      }
     }
   }
+  FILE_OPERATION_LOOP_END(FMTLOAD(TRANSFER_ERROR, (FileName)));
+
+  FTerminal->UpdateTargetAttrs(DestFullName, File, CopyParam, Attrs);
 }
 //---------------------------------------------------------------------------
 // Similar to TS3FileSystem::VerifyCertificate

+ 5 - 10
source/core/WebDAVFileSystem.h

@@ -92,16 +92,11 @@ public:
 protected:
   virtual UnicodeString __fastcall GetCurrentDirectory();
 
-  void __fastcall Sink(const UnicodeString FileName,
-    const TRemoteFile * File, const UnicodeString TargetDir,
-    const TCopyParamType * CopyParam, int Params,
-    TFileOperationProgressType * OperationProgress, unsigned int Flags,
-    TDownloadSessionAction & Action, bool & ChildError);
-  void __fastcall SinkRobust(const UnicodeString FileName,
-    const TRemoteFile * File, const UnicodeString TargetDir,
-    const TCopyParamType * CopyParam, int Params,
-    TFileOperationProgressType * OperationProgress, unsigned int Flags);
-  void __fastcall SinkFile(const UnicodeString FileName, const TRemoteFile * File, void * Param);
+  virtual void __fastcall Sink(
+    const UnicodeString & FileName, const TRemoteFile * File,
+    const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
+    const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
+    unsigned int Flags, TDownloadSessionAction & Action);
   void __fastcall ConfirmOverwrite(
     const UnicodeString & SourceFullFileName, UnicodeString & DestFileName,
     TFileOperationProgressType * OperationProgress,