mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
9444 lines
303 KiB
C++
9444 lines
303 KiB
C++
//---------------------------------------------------------------------------
|
|
#include <CorePCH.h>
|
|
#pragma hdrstop
|
|
|
|
#include "Terminal.h"
|
|
|
|
#include "PuttyTools.h"
|
|
#include "FileBuffer.h"
|
|
#include "RemoteFiles.h"
|
|
#include "SecureShell.h"
|
|
#include "ScpFileSystem.h"
|
|
#include "SftpFileSystem.h"
|
|
#include "FtpFileSystem.h"
|
|
#include "WebDAVFileSystem.h"
|
|
#include "S3FileSystem.h"
|
|
#include "Queue.h"
|
|
#include "Cryptography.h"
|
|
#include "NeonIntf.h"
|
|
#include <PuttyTools.h>
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wreserved-id-macro"
|
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
|
#include <openssl/pkcs12.h>
|
|
#include <openssl/err.h>
|
|
#pragma clang diagnostic pop
|
|
|
|
#ifndef AUTO_WINSOCK
|
|
#include <winsock2.h>
|
|
#endif
|
|
//---------------------------------------------------------------------------
|
|
#define FILE_OPERATION_LOOP_TERMINAL this
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
class TLoopDetector
|
|
{
|
|
public:
|
|
__fastcall TLoopDetector();
|
|
void __fastcall RecordVisitedDirectory(const UnicodeString & Directory);
|
|
bool __fastcall IsUnvisitedDirectory(const UnicodeString & Directory);
|
|
|
|
private:
|
|
std::unique_ptr<TStringList> FVisitedDirectories;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TLoopDetector::TLoopDetector()
|
|
{
|
|
FVisitedDirectories.reset(CreateSortedStringList());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TLoopDetector::RecordVisitedDirectory(const UnicodeString & Directory)
|
|
{
|
|
UnicodeString VisitedDirectory = ExcludeTrailingBackslash(Directory);
|
|
FVisitedDirectories->Add(VisitedDirectory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TLoopDetector::IsUnvisitedDirectory(const UnicodeString & Directory)
|
|
{
|
|
bool Result = (FVisitedDirectories->IndexOf(Directory) < 0);
|
|
|
|
if (Result)
|
|
{
|
|
RecordVisitedDirectory(Directory);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
struct TMoveFileParams
|
|
{
|
|
UnicodeString Target;
|
|
UnicodeString FileMask;
|
|
bool DontOverwrite;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
struct TFilesFindParams
|
|
{
|
|
TFileMasks FileMask;
|
|
TFileFoundEvent OnFileFound;
|
|
TFindingFileEvent OnFindingFile;
|
|
bool Cancel;
|
|
TLoopDetector LoopDetector;
|
|
UnicodeString RealDirectory;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
TCalculateSizeStats::TCalculateSizeStats()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TCalculateSizeParams::TCalculateSizeParams()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
Result = true;
|
|
AllowDirs = true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TSynchronizeOptions::TSynchronizeOptions()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TSynchronizeOptions::~TSynchronizeOptions()
|
|
{
|
|
delete Filter;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TSynchronizeOptions::MatchesFilter(const UnicodeString & FileName)
|
|
{
|
|
int FoundIndex;
|
|
bool Result;
|
|
if (Filter == NULL)
|
|
{
|
|
Result = true;
|
|
}
|
|
else
|
|
{
|
|
Result = Filter->Find(FileName, FoundIndex);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TSpaceAvailable::TSpaceAvailable()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TOverwriteFileParams::TOverwriteFileParams()
|
|
{
|
|
SourceSize = 0;
|
|
DestSize = 0;
|
|
SourcePrecision = mfFull;
|
|
DestPrecision = mfFull;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
class TTunnelThread : public TSimpleThread
|
|
{
|
|
public:
|
|
__fastcall TTunnelThread(TSecureShell * SecureShell);
|
|
virtual __fastcall ~TTunnelThread();
|
|
|
|
virtual void __fastcall Terminate();
|
|
|
|
protected:
|
|
virtual void __fastcall Execute();
|
|
|
|
private:
|
|
TSecureShell * FSecureShell;
|
|
bool FTerminated;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTunnelThread::TTunnelThread(TSecureShell * SecureShell) :
|
|
FSecureShell(SecureShell),
|
|
FTerminated(false)
|
|
{
|
|
Start();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTunnelThread::~TTunnelThread()
|
|
{
|
|
// close before the class's virtual functions (Terminate particularly) are lost
|
|
Close();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelThread::Terminate()
|
|
{
|
|
FTerminated = true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelThread::Execute()
|
|
{
|
|
try
|
|
{
|
|
while (!FTerminated)
|
|
{
|
|
FSecureShell->Idle(250);
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
if (FSecureShell->Active)
|
|
{
|
|
FSecureShell->Close();
|
|
}
|
|
// do not pass exception out of thread's proc
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
class TTunnelUI : public TSessionUI
|
|
{
|
|
public:
|
|
__fastcall TTunnelUI(TTerminal * Terminal);
|
|
virtual __fastcall ~TTunnelUI();
|
|
virtual void __fastcall Information(const UnicodeString & Str);
|
|
virtual unsigned int __fastcall QueryUser(const UnicodeString Query,
|
|
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType);
|
|
virtual unsigned int __fastcall QueryUserException(const UnicodeString Query,
|
|
Exception * E, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType);
|
|
virtual bool __fastcall PromptUser(TSessionData * Data, TPromptKind Kind,
|
|
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts,
|
|
TStrings * Results);
|
|
virtual void __fastcall DisplayBanner(const UnicodeString & Banner);
|
|
virtual void __fastcall FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpContext);
|
|
virtual void __fastcall HandleExtendedException(Exception * E);
|
|
virtual void __fastcall Closed();
|
|
virtual void __fastcall ProcessGUI();
|
|
|
|
private:
|
|
TTerminal * FTerminal;
|
|
unsigned int FTerminalThread;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTunnelUI::TTunnelUI(TTerminal * Terminal)
|
|
{
|
|
FTerminal = Terminal;
|
|
FTerminalThread = GetCurrentThreadId();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTunnelUI::~TTunnelUI()
|
|
{
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::Information(const UnicodeString & Str)
|
|
{
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
FTerminal->Information(Str);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTunnelUI::QueryUser(const UnicodeString Query,
|
|
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
unsigned int Result;
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
Result = FTerminal->QueryUser(Query, MoreMessages, Answers, Params, QueryType);
|
|
}
|
|
else
|
|
{
|
|
Result = AbortAnswer(Answers);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTunnelUI::QueryUserException(const UnicodeString Query,
|
|
Exception * E, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
unsigned int Result;
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
Result = FTerminal->QueryUserException(Query, E, Answers, Params, QueryType);
|
|
}
|
|
else
|
|
{
|
|
Result = AbortAnswer(Answers);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTunnelUI::PromptUser(TSessionData * Data, TPromptKind Kind,
|
|
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
|
|
{
|
|
bool Result;
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
if (IsAuthenticationPrompt(Kind))
|
|
{
|
|
Instructions =
|
|
FMTLOAD(TUNNEL_INSTRUCTION2, (Data->HostName)) +
|
|
(Instructions.IsEmpty() ? L"" : L"\n") +
|
|
Instructions;
|
|
}
|
|
|
|
Result = FTerminal->PromptUser(Data, Kind, Name, Instructions, Prompts, Results);
|
|
}
|
|
else
|
|
{
|
|
Result = false;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::DisplayBanner(const UnicodeString & Banner)
|
|
{
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
FTerminal->DisplayBanner(Banner);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
|
|
{
|
|
throw ESshFatal(E, Msg, HelpKeyword);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::HandleExtendedException(Exception * E)
|
|
{
|
|
if (GetCurrentThreadId() == FTerminalThread)
|
|
{
|
|
FTerminal->HandleExtendedException(E);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::Closed()
|
|
{
|
|
// noop
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTunnelUI::ProcessGUI()
|
|
{
|
|
// noop
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
class TTerminalUI : public TSessionUI
|
|
{
|
|
public:
|
|
__fastcall TTerminalUI(TTerminal * Terminal);
|
|
|
|
virtual void __fastcall Information(const UnicodeString & Str);
|
|
virtual unsigned int __fastcall QueryUser(
|
|
const UnicodeString Query, TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType);
|
|
virtual unsigned int __fastcall QueryUserException(
|
|
const UnicodeString Query, Exception * E, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType);
|
|
virtual bool __fastcall PromptUser(
|
|
TSessionData * Data, TPromptKind Kind, UnicodeString Name, UnicodeString Instructions, TStrings * Prompts,
|
|
TStrings * Results);
|
|
virtual void __fastcall DisplayBanner(const UnicodeString & Banner);
|
|
virtual void __fastcall FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpContext);
|
|
virtual void __fastcall HandleExtendedException(Exception * E);
|
|
virtual void __fastcall Closed();
|
|
virtual void __fastcall ProcessGUI();
|
|
|
|
private:
|
|
TTerminal * FTerminal;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTerminalUI::TTerminalUI(TTerminal * Terminal) :
|
|
FTerminal(Terminal)
|
|
{
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::Information(const UnicodeString & Str)
|
|
{
|
|
FTerminal->Information(Str);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminalUI::QueryUser(
|
|
const UnicodeString Query, TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
return FTerminal->QueryUser(Query, MoreMessages, Answers, Params, QueryType);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminalUI::QueryUserException(
|
|
const UnicodeString Query, Exception * E, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
return FTerminal->QueryUserException(Query, E, Answers, Params, QueryType);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminalUI::PromptUser(
|
|
TSessionData * Data, TPromptKind Kind, UnicodeString Name, UnicodeString Instructions, TStrings * Prompts,
|
|
TStrings * Results)
|
|
{
|
|
return FTerminal->PromptUser(Data, Kind, Name, Instructions, Prompts, Results);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::DisplayBanner(const UnicodeString & Banner)
|
|
{
|
|
FTerminal->DisplayBanner(Banner);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpContext)
|
|
{
|
|
FTerminal->FatalError(E, Msg, HelpContext);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::HandleExtendedException(Exception * E)
|
|
{
|
|
FTerminal->HandleExtendedException(E);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::Closed()
|
|
{
|
|
FTerminal->Closed();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalUI::ProcessGUI()
|
|
{
|
|
FTerminal->ProcessGUI();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
class TCallbackGuard
|
|
{
|
|
public:
|
|
inline __fastcall TCallbackGuard(TTerminal * FTerminal);
|
|
inline __fastcall ~TCallbackGuard();
|
|
|
|
NORETURN void __fastcall FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword);
|
|
inline void __fastcall Verify();
|
|
inline bool __fastcall Verify(Exception * E);
|
|
void __fastcall Dismiss();
|
|
|
|
private:
|
|
ExtException * FFatalError;
|
|
TTerminal * FTerminal;
|
|
bool FGuarding;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TCallbackGuard::TCallbackGuard(TTerminal * Terminal) :
|
|
FFatalError(NULL),
|
|
FTerminal(Terminal),
|
|
FGuarding(FTerminal->FCallbackGuard == NULL)
|
|
{
|
|
if (FGuarding)
|
|
{
|
|
FTerminal->FCallbackGuard = this;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TCallbackGuard::~TCallbackGuard()
|
|
{
|
|
if (FGuarding)
|
|
{
|
|
DebugAssert((FTerminal->FCallbackGuard == this) || (FTerminal->FCallbackGuard == NULL));
|
|
FTerminal->FCallbackGuard = NULL;
|
|
}
|
|
|
|
delete FFatalError;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
NORETURN void __fastcall TCallbackGuard::FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword)
|
|
{
|
|
DebugAssert(FGuarding);
|
|
|
|
// make sure we do not bother about getting back the silent abort exception
|
|
// we issued ourselves. this may happen when there is an exception handler
|
|
// that converts any exception to fatal one (such as in TTerminal::Open).
|
|
if (dynamic_cast<ECallbackGuardAbort *>(E) == NULL)
|
|
{
|
|
delete FFatalError;
|
|
FFatalError = new ExtException(E, Msg, HelpKeyword);
|
|
}
|
|
|
|
// silently abort what we are doing.
|
|
// non-silent exception would be caught probably by default application
|
|
// exception handler, which may not do an appropriate action
|
|
// (particularly it will not resume broken transfer).
|
|
throw ECallbackGuardAbort();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TCallbackGuard::Dismiss()
|
|
{
|
|
DebugAssert(FFatalError == NULL);
|
|
FGuarding = false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TCallbackGuard::Verify()
|
|
{
|
|
if (FGuarding)
|
|
{
|
|
FGuarding = false;
|
|
DebugAssert(FTerminal->FCallbackGuard == this);
|
|
FTerminal->FCallbackGuard = NULL;
|
|
|
|
if (FFatalError != NULL)
|
|
{
|
|
throw ESshFatal(FFatalError, L"");
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TCallbackGuard::Verify(Exception * E)
|
|
{
|
|
bool Result =
|
|
(dynamic_cast<ECallbackGuardAbort *>(E) != NULL);
|
|
if (Result)
|
|
{
|
|
DebugAssert(FGuarding);
|
|
DebugAssert(FFatalError != NULL);
|
|
Verify();
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
TRobustOperationLoop::TRobustOperationLoop(
|
|
TTerminal * Terminal, TFileOperationProgressType * OperationProgress, bool * AnyTransfer, bool CanRetry) :
|
|
FTerminal(Terminal),
|
|
FOperationProgress(OperationProgress),
|
|
FRetry(false),
|
|
FAnyTransfer(AnyTransfer),
|
|
FCanRetry(CanRetry)
|
|
{
|
|
if (FAnyTransfer != NULL)
|
|
{
|
|
FPrevAnyTransfer = *FAnyTransfer;
|
|
*FAnyTransfer = false;
|
|
FStart = Now();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRobustOperationLoop::~TRobustOperationLoop()
|
|
{
|
|
if (FAnyTransfer != NULL)
|
|
{
|
|
*FAnyTransfer = FPrevAnyTransfer;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TRobustOperationLoop::TryReopen(Exception & E)
|
|
{
|
|
if (!FCanRetry)
|
|
{
|
|
FRetry = false;
|
|
}
|
|
else if (dynamic_cast<ESkipFile *>(&E) != NULL)
|
|
{
|
|
FRetry = false;
|
|
}
|
|
else if (FTerminal->Active)
|
|
{
|
|
FTerminal->LogEvent(1, L"Session is open, will not retry transfer");
|
|
FRetry = false;
|
|
}
|
|
else
|
|
{
|
|
FRetry = true;
|
|
if (FAnyTransfer != NULL)
|
|
{
|
|
// if there was any transfer, reset the global timestamp
|
|
if (*FAnyTransfer)
|
|
{
|
|
FStart = Now();
|
|
FPrevAnyTransfer = true;
|
|
*FAnyTransfer = false;
|
|
}
|
|
else
|
|
{
|
|
FRetry = FTerminal->ContinueReopen(FStart);
|
|
if (!FRetry)
|
|
{
|
|
FTerminal->LogEvent(L"Retry interval expired, will not retry transfer");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FRetry)
|
|
{
|
|
FRetry =
|
|
FTerminal->QueryReopen(&E, ropNoReadDirectory, FOperationProgress);
|
|
}
|
|
}
|
|
return FRetry;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TRobustOperationLoop::ShouldRetry()
|
|
{
|
|
return FRetry;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TRobustOperationLoop::Retry()
|
|
{
|
|
bool Result = FRetry;
|
|
FRetry = false;
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
class TRetryOperationLoop
|
|
{
|
|
public:
|
|
TRetryOperationLoop(TTerminal * Terminal);
|
|
|
|
void Error(Exception & E);
|
|
void Error(Exception & E, TSessionAction & Action);
|
|
void Error(Exception & E, const UnicodeString & Message);
|
|
void Error(Exception & E, TSessionAction & Action, const UnicodeString & Message);
|
|
bool Retry();
|
|
bool Succeeded();
|
|
|
|
private:
|
|
TTerminal * FTerminal;
|
|
bool FRetry;
|
|
bool FSucceeded;
|
|
|
|
void DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message);
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
TRetryOperationLoop::TRetryOperationLoop(TTerminal * Terminal)
|
|
{
|
|
FTerminal = Terminal;
|
|
FRetry = false;
|
|
FSucceeded = true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TRetryOperationLoop::DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message)
|
|
{
|
|
FSucceeded = false;
|
|
// Note that the action may already be canceled when RollbackAction is called
|
|
unsigned int Result;
|
|
try
|
|
{
|
|
Result = FTerminal->CommandError(&E, Message, qaRetry | qaSkip | qaAbort);
|
|
}
|
|
catch (Exception & E2)
|
|
{
|
|
if (Action != NULL)
|
|
{
|
|
FTerminal->RollbackAction(*Action, NULL, &E2);
|
|
}
|
|
throw;
|
|
}
|
|
|
|
switch (Result)
|
|
{
|
|
case qaRetry:
|
|
FRetry = true;
|
|
if (Action != NULL)
|
|
{
|
|
Action->Cancel();
|
|
}
|
|
break;
|
|
|
|
case qaAbort:
|
|
if (Action != NULL)
|
|
{
|
|
FTerminal->RollbackAction(*Action, NULL, &E);
|
|
}
|
|
Abort();
|
|
break;
|
|
|
|
case qaSkip:
|
|
if (Action != NULL)
|
|
{
|
|
Action->Cancel();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DebugFail();
|
|
break;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TRetryOperationLoop::Error(Exception & E)
|
|
{
|
|
DoError(E, NULL, UnicodeString());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action)
|
|
{
|
|
DoError(E, &Action, UnicodeString());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TRetryOperationLoop::Error(Exception & E, const UnicodeString & Message)
|
|
{
|
|
DoError(E, NULL, Message);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action, const UnicodeString & Message)
|
|
{
|
|
DoError(E, &Action, Message);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TRetryOperationLoop::Retry()
|
|
{
|
|
bool Result = FRetry;
|
|
FRetry = false;
|
|
if (Result)
|
|
{
|
|
FSucceeded = true;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TRetryOperationLoop::Succeeded()
|
|
{
|
|
return FSucceeded;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
TCollectedFileList::TCollectedFileList()
|
|
{
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TCollectedFileList::~TCollectedFileList()
|
|
{
|
|
int ACount = Count();
|
|
for (int Index = 0; Index < ACount; Index++)
|
|
{
|
|
Deleting(Index);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TCollectedFileList::Deleting(int Index)
|
|
{
|
|
delete FList[Index].Object;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int TCollectedFileList::Add(const UnicodeString & FileName, TObject * Object, bool Dir)
|
|
{
|
|
TFileData Data;
|
|
Data.FileName = FileName;
|
|
Data.Object = Object;
|
|
Data.Dir = Dir;
|
|
Data.Recursed = true;
|
|
Data.State = 0;
|
|
FList.push_back(Data);
|
|
return Count() - 1;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TCollectedFileList::DidNotRecurse(int Index)
|
|
{
|
|
FList[Index].Recursed = false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TCollectedFileList::Delete(int Index)
|
|
{
|
|
Deleting(Index);
|
|
FList.erase(FList.begin() + Index);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int TCollectedFileList::Count() const
|
|
{
|
|
return SizeToIntChecked(FList.size());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString TCollectedFileList::GetFileName(int Index) const
|
|
{
|
|
return FList[Index].FileName;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TObject * TCollectedFileList::GetObject(int Index) const
|
|
{
|
|
return FList[Index].Object;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TCollectedFileList::IsDir(int Index) const
|
|
{
|
|
return FList[Index].Dir;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TCollectedFileList::IsRecursed(int Index) const
|
|
{
|
|
return FList[Index].Recursed;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int TCollectedFileList::GetState(int Index) const
|
|
{
|
|
return FList[Index].State;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TCollectedFileList::SetState(int Index, int State)
|
|
{
|
|
FList[Index].State = State;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
TParallelOperation::TParallelOperation(TOperationSide Side)
|
|
{
|
|
FCopyParam = NULL;
|
|
FParams = 0;
|
|
FProbablyEmpty = false;
|
|
FClients = 0;
|
|
FMainOperationProgress = NULL;
|
|
DebugAssert((Side == osLocal) || (Side == osRemote));
|
|
FSide = Side;
|
|
FVersion = 0;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TParallelOperation::Init(
|
|
TStrings * AFileList, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TFileOperationProgressType * MainOperationProgress, const UnicodeString & MainName,
|
|
__int64 ParallelFileSize)
|
|
{
|
|
DebugAssert(FFileList.get() == NULL);
|
|
// More lists should really happen in scripting only, which does not support parallel transfers atm.
|
|
// But in general the code should work with more lists anyway, it just was not tested for it.
|
|
DebugAssert(AFileList->Count == 1);
|
|
FFileList.reset(AFileList);
|
|
FSection.reset(new TCriticalSection());
|
|
FTargetDir = TargetDir;
|
|
FCopyParam = CopyParam;
|
|
FParams = Params;
|
|
FMainOperationProgress = MainOperationProgress;
|
|
FMainName = MainName;
|
|
FListIndex = 0;
|
|
FIndex = 0;
|
|
FIsParallelFileTransfer = (ParallelFileSize >= 0);
|
|
FParallelFileSize = ParallelFileSize;
|
|
FParallelFileOffset = 0;
|
|
FParallelFileCount = 0;
|
|
FParallelFileMerging = false;
|
|
FParallelFileMerged = 0;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TParallelOperation::~TParallelOperation()
|
|
{
|
|
WaitFor();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TParallelOperation::IsInitialized()
|
|
{
|
|
return (FMainOperationProgress != NULL);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TParallelOperation::ShouldAddClient()
|
|
{
|
|
bool Result;
|
|
// TTransferQueueItem::ProgressUpdated() already checks IsInitialized() before calling this
|
|
if (!IsInitialized())
|
|
{
|
|
Result = false;
|
|
}
|
|
else
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
Result = !FProbablyEmpty && (FMainOperationProgress->Cancel < csCancel);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TParallelOperation::AddClient()
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
FClients++;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TParallelOperation::RemoveClient()
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
FClients--;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TParallelOperation::WaitFor()
|
|
{
|
|
// Even initialized?
|
|
// Won't be, when parallel transfers were not possible (like when preserving of directory timestamps is enabled)
|
|
if (FSection.get() != NULL)
|
|
{
|
|
bool Done;
|
|
|
|
do
|
|
{
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
Done = (FClients == 0);
|
|
}
|
|
|
|
if (!Done)
|
|
{
|
|
// propagate the total progress incremented by the parallel operations
|
|
FMainOperationProgress->Progress();
|
|
Sleep(200);
|
|
}
|
|
}
|
|
while (!Done);
|
|
FProbablyEmpty = true;
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(FClients == 0);
|
|
}
|
|
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TParallelOperation::Done(
|
|
const UnicodeString & FileName, bool Dir, bool Success, const UnicodeString & TargetDir,
|
|
const TCopyParamType * CopyParam, TTerminal * Terminal)
|
|
{
|
|
if (Dir)
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
|
|
TDirectories::iterator DirectoryIterator = FDirectories.find(FileName);
|
|
if (DebugAlwaysTrue(DirectoryIterator != FDirectories.end()))
|
|
{
|
|
if (Success)
|
|
{
|
|
DebugAssert(!DirectoryIterator->second.Exists);
|
|
DirectoryIterator->second.Exists = true;
|
|
}
|
|
else
|
|
{
|
|
// This is actually not useful at the moment, as when creating directory fails and "Skip" is pressed,
|
|
// the current code in CopyToRemote/CreateDirectory will behave as, if it succeded, so Success will be true here.
|
|
FDirectories.erase(DirectoryIterator);
|
|
if (FFileList->Count > FListIndex)
|
|
{
|
|
UnicodeString FileNameWithSlash = UniversalIncludeTrailingBackslash((FSide == osRemote), FileName);
|
|
|
|
// It can actually be a different list than the one the directory was taken from,
|
|
// but that does not matter that much. It should not happen anyway, as more lists should be in scripting only.
|
|
TCollectedFileList * Files = GetFileList(FListIndex);
|
|
int Index = 0;
|
|
while (Index < Files->Count())
|
|
{
|
|
if (StartsText(FileNameWithSlash, Files->GetFileName(Index)))
|
|
{
|
|
// We should add the file to "skip" counters in the OperationProgress,
|
|
// but an interactive foreground transfer is not doing that either yet.
|
|
Files->Delete(Index);
|
|
// Force re-update
|
|
FVersion++;
|
|
if (Index < FIndex)
|
|
{
|
|
FIndex--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Index++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsParallelFileTransfer)
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
|
|
try
|
|
{
|
|
if (Success && DebugAlwaysTrue(FSide == osRemote))
|
|
{
|
|
TParallelFileOffsets::const_iterator I = std::find(FParallelFileOffsets.begin(), FParallelFileOffsets.end(), CopyParam->PartOffset);
|
|
if (DebugAlwaysTrue(I != FParallelFileOffsets.end()))
|
|
{
|
|
int Index = SizeToIntChecked(I - FParallelFileOffsets.begin());
|
|
DebugAssert(!FParallelFileDones[Index]);
|
|
FParallelFileDones[Index] = true;
|
|
|
|
if (!FParallelFileMerging)
|
|
{
|
|
// Once we obtain "merging" semaphore, we won't leave until everything is merged
|
|
TAutoFlag MergingFlag(FParallelFileMerging);
|
|
|
|
try
|
|
{
|
|
UnicodeString TargetName = CombinePaths(TargetDir, FParallelFileTargetName);
|
|
UnicodeString TargetNamePartial = TargetName + PartialExt;
|
|
UnicodeString TargetNamePartialOnly = ExtractFileName(TargetNamePartial);
|
|
|
|
while (true)
|
|
{
|
|
if ((FParallelFileMerged >= FParallelFileCount) || !FParallelFileDones[FParallelFileMerged])
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
TUnguard Unguard(FSection.get());
|
|
|
|
UnicodeString FileNameOnly = UnixExtractFileName(FileName);
|
|
// Safe as write access to FParallelFileMerged is guarded by FParallelFileMerging
|
|
int Index = FParallelFileMerged;
|
|
UnicodeString TargetPartName = GetPartPrefix(CombinePaths(TargetDir, FileNameOnly)) + IntToStr(Index);
|
|
|
|
if ((CopyParam->PartSize >= 0) && (Terminal->OperationProgress->TransferredSize != CopyParam->PartSize))
|
|
{
|
|
UnicodeString TransferredSizeStr = IntToStr(Terminal->OperationProgress->TransferredSize);
|
|
UnicodeString PartSizeStr = IntToStr(CopyParam->PartSize);
|
|
UnicodeString Message =
|
|
FMTLOAD(INCONSISTENT_SIZE, (TargetPartName, TransferredSizeStr, PartSizeStr));
|
|
Terminal->TerminalError(NULL, Message);
|
|
}
|
|
|
|
if (Index == 0)
|
|
{
|
|
Terminal->LogEvent(FORMAT(L"Renaming part %d of \"%s\" to \"%s\"...", (Index, FileNameOnly, TargetNamePartialOnly)));
|
|
Terminal->DoRenameLocalFileForce(TargetPartName, TargetNamePartial);
|
|
}
|
|
else
|
|
{
|
|
{
|
|
Terminal->LogEvent(FORMAT(L"Appending part %d of \"%s\" to \"%s\"...", (Index, FileNameOnly, TargetNamePartialOnly)));
|
|
std::unique_ptr<THandleStream> SourceStream(TSafeHandleStream::CreateFromFile(TargetPartName, fmOpenRead | fmShareDenyWrite));
|
|
std::unique_ptr<THandleStream> DestStream(TSafeHandleStream::CreateFromFile(TargetNamePartial, fmOpenWrite | fmShareDenyWrite));
|
|
HANDLE DestHandle = reinterpret_cast<HANDLE>(DestStream->Handle);
|
|
FILETIME WrTime;
|
|
bool GotWrTime = GetFileTime(DestHandle, NULL, NULL, &WrTime);
|
|
DestStream->Seek(0L, soEnd);
|
|
DestStream->CopyFrom(SourceStream.get(), SourceStream->Size);
|
|
if (GotWrTime)
|
|
{
|
|
SetFileTime(DestHandle, NULL, NULL, &WrTime);
|
|
}
|
|
}
|
|
|
|
Terminal->DoDeleteLocalFile(TargetPartName);
|
|
}
|
|
|
|
FParallelFileMerged++;
|
|
}
|
|
}
|
|
|
|
if ((FParallelFileMerged == FParallelFileCount) && (FParallelFileOffset == FParallelFileSize))
|
|
{
|
|
Terminal->LogEvent(FORMAT(L"Renaming completed \"%s\" to \"%s\"...", (TargetNamePartialOnly, FParallelFileTargetName)));
|
|
Terminal->DoRenameLocalFileForce(TargetNamePartial, TargetName);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
Success = false;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (!Success)
|
|
{
|
|
// If any part fails, cancel whole transfer.
|
|
// We should get here only after the user sees/acknowledges the failure.
|
|
FMainOperationProgress->SetCancel(csCancel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TParallelOperation::CheckEnd(TCollectedFileList * Files)
|
|
{
|
|
bool Result = (FIndex >= Files->Count());
|
|
if (Result)
|
|
{
|
|
FListIndex++;
|
|
FIndex = 0;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TParallelOperation::GetOnlyFile(TStrings * FileList, UnicodeString & FileName, TObject *& Object)
|
|
{
|
|
bool Result = (FileList->Count == 1);
|
|
if (Result)
|
|
{
|
|
TCollectedFileList * OnlyFileList = GetFileList(FileList, 0);
|
|
Result = (OnlyFileList->Count() == 1) && !OnlyFileList->IsDir(0);
|
|
if (Result)
|
|
{
|
|
FileName = OnlyFileList->GetFileName(0);
|
|
Object = OnlyFileList->GetObject(0);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TCollectedFileList * TParallelOperation::GetFileList(TStrings * FileList, int Index)
|
|
{
|
|
return DebugNotNull(dynamic_cast<TCollectedFileList *>(FileList->Objects[Index]));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TCollectedFileList * TParallelOperation::GetFileList(int Index)
|
|
{
|
|
return GetFileList(FFileList.get(), Index);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString TParallelOperation::GetPartPrefix(const UnicodeString & FileName)
|
|
{
|
|
return FORMAT(L"%s%s.", (FileName, PartialExt));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int TParallelOperation::GetNext(
|
|
TTerminal * Terminal, UnicodeString & FileName, TObject *& Object, UnicodeString & TargetDir, bool & Dir,
|
|
bool & Recursed, TCopyParamType *& CustomCopyParam)
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
int Result = 1;
|
|
TCollectedFileList * Files;
|
|
do
|
|
{
|
|
if (FFileList->Count > FListIndex)
|
|
{
|
|
Files = GetFileList(FListIndex);
|
|
// can happen if the file was excluded by file mask
|
|
if (CheckEnd(Files))
|
|
{
|
|
Files = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Files = NULL;
|
|
Result = -1;
|
|
}
|
|
}
|
|
while ((Result == 1) && (Files == NULL));
|
|
|
|
if (Files != NULL)
|
|
{
|
|
UnicodeString RootPath = FFileList->Strings[FListIndex];
|
|
|
|
FileName = Files->GetFileName(FIndex);
|
|
Object = Files->GetObject(FIndex);
|
|
Dir = Files->IsDir(FIndex);
|
|
Recursed = Files->IsRecursed(FIndex);
|
|
UnicodeString DirPath;
|
|
bool FirstLevel;
|
|
if (FSide == osLocal)
|
|
{
|
|
DirPath = ExtractFileDir(FileName);
|
|
FirstLevel = SamePaths(DirPath, RootPath);
|
|
}
|
|
else
|
|
{
|
|
DirPath = UnixExtractFileDir(FileName);
|
|
FirstLevel = UnixSamePath(DirPath, RootPath);
|
|
}
|
|
|
|
if (FirstLevel)
|
|
{
|
|
TargetDir = FTargetDir;
|
|
}
|
|
else
|
|
{
|
|
TDirectories::const_iterator DirectoryIterator = FDirectories.find(DirPath);
|
|
if (DebugAlwaysFalse(DirectoryIterator == FDirectories.end()))
|
|
{
|
|
throw EInvalidOperation(L"Parent path not known");
|
|
}
|
|
const TDirectoryData & DirectoryData = DirectoryIterator->second;
|
|
if (!DirectoryData.Exists)
|
|
{
|
|
Result = 0; // wait for parent directory to be created
|
|
}
|
|
else
|
|
{
|
|
TargetDir = DirectoryData.OppositePath;
|
|
}
|
|
}
|
|
|
|
if (!TargetDir.IsEmpty())
|
|
{
|
|
UnicodeString OnlyFileName;
|
|
if (Dir || IsParallelFileTransfer) // optimization
|
|
{
|
|
if (FSide == osLocal)
|
|
{
|
|
OnlyFileName = ExtractFileName(FileName);
|
|
}
|
|
else
|
|
{
|
|
OnlyFileName = UnixExtractFileName(FileName);
|
|
}
|
|
OnlyFileName = Terminal->ChangeFileName(FCopyParam, OnlyFileName, FSide, FirstLevel);
|
|
}
|
|
|
|
if (Dir)
|
|
{
|
|
DebugAssert(!OnlyFileName.IsEmpty());
|
|
TDirectoryData DirectoryData;
|
|
DirectoryData.OppositePath = UniversalCombinePaths((FSide == osLocal), TargetDir, OnlyFileName);
|
|
DirectoryData.Exists = false;
|
|
|
|
FDirectories.insert(std::make_pair(FileName, DirectoryData));
|
|
}
|
|
|
|
bool Processed = true;
|
|
if (IsParallelFileTransfer)
|
|
{
|
|
CustomCopyParam = new TCopyParamType(*FCopyParam);
|
|
CustomCopyParam->PartOffset = FParallelFileOffset;
|
|
__int64 Remaining = FParallelFileSize - CustomCopyParam->PartOffset;
|
|
CustomCopyParam->PartSize = FParallelFileSize / Configuration->QueueTransfersLimit;
|
|
DebugAssert(!OnlyFileName.IsEmpty());
|
|
if (FParallelFileTargetName.IsEmpty())
|
|
{
|
|
FParallelFileTargetName = OnlyFileName;
|
|
}
|
|
DebugAssert(FParallelFileTargetName == OnlyFileName);
|
|
int Index = FParallelFileCount;
|
|
UnicodeString PartFileName = GetPartPrefix(OnlyFileName) + IntToStr(Index);
|
|
FParallelFileCount++;
|
|
FParallelFileOffsets.push_back(CustomCopyParam->PartOffset);
|
|
FParallelFileDones.push_back(false);
|
|
DebugAssert(FParallelFileOffsets.size() == static_cast<size_t>(FParallelFileCount));
|
|
CustomCopyParam->FileMask = DelimitFileNameMask(PartFileName);
|
|
if ((CustomCopyParam->PartSize >= Remaining) ||
|
|
(Remaining - CustomCopyParam->PartSize < CustomCopyParam->PartSize / 10))
|
|
{
|
|
CustomCopyParam->PartSize = -1; // Until the end
|
|
FParallelFileOffset = FParallelFileSize;
|
|
Terminal->LogEvent(FORMAT(L"Starting transfer of \"%s\" part %d from %s until the EOF", (OnlyFileName, Index, IntToStr(CustomCopyParam->PartOffset))));
|
|
}
|
|
else
|
|
{
|
|
Processed = false;
|
|
FParallelFileOffset += CustomCopyParam->PartSize;
|
|
Terminal->LogEvent(FORMAT(L"Starting transfer of \"%s\" part %d from %s, length %s", (OnlyFileName, Index, IntToStr(CustomCopyParam->PartOffset), IntToStr(CustomCopyParam->PartSize))));
|
|
}
|
|
}
|
|
|
|
if (Processed)
|
|
{
|
|
// The current implementation of UpdateFileList relies on this specific way of changing state
|
|
// (all files before FIndex one-by-one)
|
|
Files->SetState(FIndex, qfsProcessed);
|
|
FIndex++;
|
|
CheckEnd(Files);
|
|
}
|
|
}
|
|
}
|
|
|
|
FProbablyEmpty = (FFileList->Count == FListIndex);
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TParallelOperation::UpdateFileList(TQueueFileList * UpdateFileList)
|
|
{
|
|
TGuard Guard(FSection.get());
|
|
|
|
bool Result =
|
|
(UpdateFileList->FLastParallelOperation != this) ||
|
|
(UpdateFileList->FLastParallelOperationVersion != FVersion);
|
|
|
|
DebugAssert(FFileList->Count == 1);
|
|
TCollectedFileList * Files = GetFileList(0);
|
|
|
|
if (!Result && (UpdateFileList->GetCount() != Files->Count()))
|
|
{
|
|
DebugAssert(false);
|
|
Result = true;
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
UpdateFileList->Clear();
|
|
for (int Index = 0; Index < Files->Count(); Index++)
|
|
{
|
|
UpdateFileList->Add(Files->GetFileName(Index), Files->GetState(Index));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int Index = ((FListIndex == 0) ? FIndex : Files->Count()) - 1;
|
|
while ((Index >= 0) && (UpdateFileList->GetState(Index) != Files->GetState(Index)))
|
|
{
|
|
UpdateFileList->SetState(Index, Files->GetState(Index));
|
|
Index--;
|
|
Result = true;
|
|
}
|
|
}
|
|
|
|
UpdateFileList->FLastParallelOperation = this;
|
|
UpdateFileList->FLastParallelOperationVersion = FVersion;
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTerminal::TTerminal(TSessionData * SessionData, TConfiguration * Configuration, TActionLog * ActionLog)
|
|
{
|
|
FConfiguration = Configuration;
|
|
FSessionData = new TSessionData(L"");
|
|
FSessionData->Assign(SessionData);
|
|
// Cache it, in case it changes (particularly by ConfigureTunnel)
|
|
FPasswordEncryptionKey = SessionData->GetSessionPasswordEncryptionKey();
|
|
TDateTime Started = Now(); // use the same time for session and XML log
|
|
FTerminalUI = new TTerminalUI(this);
|
|
FLog = new TSessionLog(FTerminalUI, Started, FSessionData, Configuration);
|
|
if (ActionLog != NULL)
|
|
{
|
|
FActionLog = ActionLog;
|
|
FActionLogOwned = false;
|
|
}
|
|
else
|
|
{
|
|
FActionLog = new TActionLog(FTerminalUI, Started, FSessionData, Configuration);
|
|
FActionLogOwned = true;
|
|
}
|
|
FFiles = new TRemoteDirectory(this);
|
|
FExceptionOnFail = 0;
|
|
FInTransaction = 0;
|
|
FReadCurrentDirectoryPending = false;
|
|
FReadDirectoryPending = false;
|
|
FUsersGroupsLookedup = False;
|
|
FTunnelLocalPortNumber = 0;
|
|
FFileSystem = NULL;
|
|
FSecureShell = NULL;
|
|
FOnProgress = NULL;
|
|
FOnFinished = NULL;
|
|
FOnDeleteLocalFile = NULL;
|
|
FOnReadDirectoryProgress = NULL;
|
|
FOnQueryUser = NULL;
|
|
FOnPromptUser = NULL;
|
|
FOnDisplayBanner = NULL;
|
|
FOnShowExtendedException = NULL;
|
|
FOnInformation = NULL;
|
|
FOnCustomCommand = NULL;
|
|
FOnClose = NULL;
|
|
FOnFindingFile = NULL;
|
|
FOperationProgressPersistence = NULL;
|
|
FOperationProgressOnceDoneOperation = odoIdle;
|
|
|
|
FUseBusyCursor = True;
|
|
FDirectoryCache = new TRemoteDirectoryCache();
|
|
FDirectoryChangesCache = NULL;
|
|
FFSProtocol = cfsUnknown;
|
|
FCommandSession = NULL;
|
|
FAutoReadDirectory = true;
|
|
FReadingCurrentDirectory = false;
|
|
FStatus = ssClosed;
|
|
FOpening = 0;
|
|
FTunnelThread = NULL;
|
|
FTunnel = NULL;
|
|
FTunnelData = NULL;
|
|
FTunnelLog = NULL;
|
|
FTunnelUI = NULL;
|
|
FTunnelOpening = false;
|
|
FCallbackGuard = NULL;
|
|
FNesting = 0;
|
|
FRememberedPasswordKind = TPromptKind(-1);
|
|
FSecondaryTerminals = 0;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTerminal::~TTerminal()
|
|
{
|
|
if (Active)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
if (FCallbackGuard != NULL)
|
|
{
|
|
// see TTerminal::HandleExtendedException
|
|
FCallbackGuard->Dismiss();
|
|
}
|
|
DebugAssert(FTunnel == NULL);
|
|
|
|
SAFE_DESTROY(FCommandSession);
|
|
|
|
if (SessionData->CacheDirectoryChanges && SessionData->PreserveDirectoryChanges &&
|
|
(FDirectoryChangesCache != NULL))
|
|
{
|
|
Configuration->SaveDirectoryChangesCache(SessionData->SessionKey,
|
|
FDirectoryChangesCache);
|
|
}
|
|
|
|
SAFE_DESTROY_EX(TCustomFileSystem, FFileSystem);
|
|
SAFE_DESTROY_EX(TSessionLog, FLog);
|
|
if (FActionLogOwned)
|
|
{
|
|
SAFE_DESTROY_EX(TActionLog, FActionLog);
|
|
}
|
|
else
|
|
{
|
|
FActionLog = NULL;
|
|
}
|
|
delete FFiles;
|
|
delete FDirectoryCache;
|
|
delete FDirectoryChangesCache;
|
|
SAFE_DESTROY(FSessionData);
|
|
SAFE_DESTROY_EX(TTerminalUI, FTerminalUI);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Idle()
|
|
{
|
|
// Once we disconnect, do nothing, until reconnect handler
|
|
// "receives the information".
|
|
// Never go idle when called from within ::ProcessGUI() call
|
|
// as we may recurse for good, timeouting eventually.
|
|
if (Active && (FNesting == 0))
|
|
{
|
|
TAutoNestingCounter NestingCounter(FNesting);
|
|
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
LogEvent(L"Session upkeep");
|
|
}
|
|
|
|
DebugAssert(FFileSystem != NULL);
|
|
FFileSystem->Idle();
|
|
|
|
if (CommandSessionOpened)
|
|
{
|
|
try
|
|
{
|
|
FCommandSession->Idle();
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
// If the secondary session is dropped, ignore the error and let
|
|
// it be reconnected when needed.
|
|
// BTW, non-fatal error can hardly happen here, that's why
|
|
// it is displayed, because it can be useful to know.
|
|
if (FCommandSession->Active)
|
|
{
|
|
FCommandSession->HandleExtendedException(&E);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------
|
|
RawByteString __fastcall TTerminal::EncryptPassword(const UnicodeString & Password)
|
|
{
|
|
return Configuration->EncryptPassword(Password, FPasswordEncryptionKey);
|
|
}
|
|
//---------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::DecryptPassword(const RawByteString & Password)
|
|
{
|
|
UnicodeString Result;
|
|
try
|
|
{
|
|
Result = Configuration->DecryptPassword(Password, FPasswordEncryptionKey);
|
|
}
|
|
catch(EAbort &)
|
|
{
|
|
// silently ignore aborted prompts for master password and return empty password
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::RecryptPasswords()
|
|
{
|
|
FSessionData->RecryptPasswords();
|
|
FRememberedPassword = EncryptPassword(GetRememberedPassword());
|
|
FRememberedTunnelPassword = EncryptPassword(GetRememberedTunnelPassword());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::ExpandFileName(UnicodeString Path,
|
|
const UnicodeString BasePath)
|
|
{
|
|
// replace this by AbsolutePath()
|
|
Path = UnixExcludeTrailingBackslash(Path);
|
|
if (!UnixIsAbsolutePath(Path) && !BasePath.IsEmpty())
|
|
{
|
|
// TODO: Handle more complicated cases like "../../xxx"
|
|
if (Path == PARENTDIRECTORY)
|
|
{
|
|
Path = UnixExcludeTrailingBackslash(UnixExtractFilePath(
|
|
UnixExcludeTrailingBackslash(BasePath)));
|
|
}
|
|
else
|
|
{
|
|
Path = UnixIncludeTrailingBackslash(BasePath) + Path;
|
|
}
|
|
}
|
|
return Path;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetActive()
|
|
{
|
|
return (FFileSystem != NULL) && FFileSystem->GetActive();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Close()
|
|
{
|
|
FFileSystem->Close();
|
|
|
|
// Cannot rely on CommandSessionOpened here as Status is set to ssClosed too late
|
|
if ((FCommandSession != NULL) && FCommandSession->Active)
|
|
{
|
|
// prevent recursion
|
|
FCommandSession->OnClose = NULL;
|
|
FCommandSession->Close();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ResetConnection()
|
|
{
|
|
// used to be called from Reopen(), why?
|
|
FTunnelError = L"";
|
|
|
|
FRememberedPasswordTried = false;
|
|
FRememberedTunnelPasswordTried = false;
|
|
|
|
if (FDirectoryChangesCache != NULL)
|
|
{
|
|
delete FDirectoryChangesCache;
|
|
FDirectoryChangesCache = NULL;
|
|
}
|
|
|
|
FFiles->Directory = L"";
|
|
// note that we cannot clear contained files
|
|
// as they can still be referenced in the GUI atm
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FingerprintScan(UnicodeString & SHA256, UnicodeString & SHA1, UnicodeString & MD5)
|
|
{
|
|
SessionData->FingerprintScan = true;
|
|
try
|
|
{
|
|
Open();
|
|
// we should never get here
|
|
DebugFail();
|
|
Abort();
|
|
}
|
|
catch (...)
|
|
{
|
|
if (!FFingerprintScannedSHA256.IsEmpty() ||
|
|
!FFingerprintScannedSHA1.IsEmpty() ||
|
|
!FFingerprintScannedMD5.IsEmpty())
|
|
{
|
|
SHA256 = FFingerprintScannedSHA256;
|
|
SHA1 = FFingerprintScannedSHA1;
|
|
MD5 = FFingerprintScannedMD5;
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Open()
|
|
{
|
|
AnySession = true;
|
|
Configuration->Usage->Inc(L"SessionOpens");
|
|
TAutoNestingCounter OpeningCounter(FOpening);
|
|
ReflectSettings();
|
|
try
|
|
{
|
|
FEncryptKey = HexToBytes(FSessionData->EncryptKey);
|
|
if (IsEncryptingFiles())
|
|
{
|
|
ValidateEncryptKey(FEncryptKey);
|
|
}
|
|
|
|
DoInformation(L"", 1);
|
|
try
|
|
{
|
|
FRememberedPasswordUsed = false;
|
|
try
|
|
{
|
|
ResetConnection();
|
|
FStatus = ssOpening;
|
|
|
|
try
|
|
{
|
|
if (FFileSystem == NULL)
|
|
{
|
|
Log->AddSystemInfo();
|
|
DoInitializeLog();
|
|
Log->AddStartupInfo();
|
|
}
|
|
|
|
DebugAssert(FTunnel == NULL);
|
|
if (FSessionData->Tunnel)
|
|
{
|
|
Information(LoadStr(OPEN_TUNNEL));
|
|
LogEvent(L"Opening tunnel.");
|
|
OpenTunnel();
|
|
Log->AddSeparator();
|
|
|
|
FSessionData->ConfigureTunnel(FTunnelLocalPortNumber);
|
|
|
|
Information(LoadStr(USING_TUNNEL));
|
|
LogEvent(FORMAT(L"Connecting via tunnel interface %s:%d.",
|
|
(FSessionData->HostNameExpanded, FSessionData->PortNumber)));
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(FTunnelLocalPortNumber == 0);
|
|
}
|
|
|
|
if (FFileSystem == NULL)
|
|
{
|
|
if (SessionData->FSProtocol == fsFTP)
|
|
{
|
|
FFSProtocol = cfsFTP;
|
|
FFileSystem = new TFTPFileSystem(this);
|
|
FFileSystem->Open();
|
|
Log->AddSeparator();
|
|
LogEvent(L"Using FTP protocol.");
|
|
}
|
|
else if (SessionData->FSProtocol == fsWebDAV)
|
|
{
|
|
FFSProtocol = cfsWebDAV;
|
|
FFileSystem = new TWebDAVFileSystem(this);
|
|
FFileSystem->Open();
|
|
Log->AddSeparator();
|
|
LogEvent(L"Using WebDAV protocol.");
|
|
}
|
|
else if (SessionData->FSProtocol == fsS3)
|
|
{
|
|
FFSProtocol = cfsS3;
|
|
FFileSystem = new TS3FileSystem(this);
|
|
FFileSystem->Open();
|
|
Log->AddSeparator();
|
|
LogEvent(L"Using S3 protocol.");
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(FSecureShell == NULL);
|
|
try
|
|
{
|
|
FSecureShell = new TSecureShell(FTerminalUI, FSessionData, Log, Configuration);
|
|
try
|
|
{
|
|
// there will be only one channel in this session
|
|
FSecureShell->Simple = true;
|
|
FSecureShell->Open();
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
DebugAssert(!FSecureShell->Active);
|
|
if (SessionData->FingerprintScan)
|
|
{
|
|
FSecureShell->GetHostKeyFingerprint(FFingerprintScannedSHA256, FFingerprintScannedMD5);
|
|
FFingerprintScannedSHA1 = UnicodeString();
|
|
}
|
|
// Tunnel errors that happens only once we connect to the local port (server-side forwarding problems).
|
|
// Contrary to local side problems handled already in OpenTunnel.
|
|
if (!FSecureShell->Active && !FTunnelError.IsEmpty())
|
|
{
|
|
// the only case where we expect this to happen (first in GUI, the latter in scripting)
|
|
DebugAssert((E.Message == LoadStr(UNEXPECTED_CLOSE_ERROR)) || (E.Message == LoadStr(NET_TRANSL_CONN_ABORTED)) || (E.Message.Pos(LoadStr(NOT_CONNECTED)) > 0));
|
|
FatalError(&E, FMTLOAD(TUNNEL_ERROR, (FTunnelError)));
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
Log->AddSeparator();
|
|
|
|
if ((SessionData->FSProtocol == fsSCPonly) ||
|
|
(SessionData->FSProtocol == fsSFTP && FSecureShell->SshFallbackCmd()))
|
|
{
|
|
FFSProtocol = cfsSCP;
|
|
FFileSystem = new TSCPFileSystem(this, FSecureShell);
|
|
FSecureShell = NULL; // ownership passed
|
|
LogEvent(L"Using SCP protocol.");
|
|
}
|
|
else
|
|
{
|
|
FFSProtocol = cfsSFTP;
|
|
FFileSystem = new TSFTPFileSystem(this, FSecureShell);
|
|
FSecureShell = NULL; // ownership passed
|
|
LogEvent(L"Using SFTP protocol.");
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
delete FSecureShell;
|
|
FSecureShell = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FFileSystem->Open();
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (FSessionData->Tunnel)
|
|
{
|
|
FSessionData->RollbackTunnel();
|
|
}
|
|
}
|
|
|
|
if (SessionData->CacheDirectoryChanges)
|
|
{
|
|
DebugAssert(FDirectoryChangesCache == NULL);
|
|
FDirectoryChangesCache = new TRemoteDirectoryChangesCache(
|
|
Configuration->CacheDirectoryChangesMaxSize);
|
|
if (SessionData->PreserveDirectoryChanges)
|
|
{
|
|
Configuration->LoadDirectoryChangesCache(SessionData->SessionKey,
|
|
FDirectoryChangesCache);
|
|
}
|
|
}
|
|
|
|
DoStartup();
|
|
|
|
if (FCollectFileSystemUsage)
|
|
{
|
|
FFileSystem->CollectUsage();
|
|
FCollectFileSystemUsage = false;
|
|
}
|
|
|
|
Information(LoadStr(STATUS_READY));
|
|
FStatus = ssOpened;
|
|
}
|
|
catch(...)
|
|
{
|
|
// rollback
|
|
if (FDirectoryChangesCache != NULL)
|
|
{
|
|
delete FDirectoryChangesCache;
|
|
FDirectoryChangesCache = NULL;
|
|
}
|
|
if (SessionData->FingerprintScan && (FFileSystem != NULL) &&
|
|
DebugAlwaysTrue(SessionData->Ftps != ftpsNone))
|
|
{
|
|
const TSessionInfo & SessionInfo = FFileSystem->GetSessionInfo();
|
|
FFingerprintScannedSHA256 = SessionInfo.CertificateFingerprintSHA256;
|
|
FFingerprintScannedSHA1 = SessionInfo.CertificateFingerprintSHA1;
|
|
FFingerprintScannedMD5 = UnicodeString();
|
|
}
|
|
if (FRememberedPasswordUsed)
|
|
{
|
|
// Particularly to prevent reusing a wrong client certificate passphrase
|
|
// in the next login attempt
|
|
FRememberedPassword = UnicodeString();
|
|
FRememberedPasswordKind = TPromptKind(-1);
|
|
FRememberedTunnelPassword = UnicodeString();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
// This does not make it through, if terminal thread is abandoned,
|
|
// see also TTerminalManager::DoConnectTerminal
|
|
DoInformation(L"", 0);
|
|
}
|
|
}
|
|
catch(EFatal &)
|
|
{
|
|
throw;
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
// any exception while opening session is fatal
|
|
FatalError(&E, L"");
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::IsListenerFree(unsigned int PortNumber)
|
|
{
|
|
SOCKET Socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
bool Result = (Socket != INVALID_SOCKET);
|
|
if (Result)
|
|
{
|
|
SOCKADDR_IN Address;
|
|
|
|
memset(&Address, 0, sizeof(Address));
|
|
Address.sin_family = AF_INET;
|
|
Address.sin_port = htons(static_cast<short>(PortNumber));
|
|
Address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
Result = (bind(Socket, reinterpret_cast<sockaddr *>(&Address), sizeof(Address)) == 0);
|
|
closesocket(Socket);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OpenTunnel()
|
|
{
|
|
DebugAssert(FTunnelData == NULL);
|
|
|
|
FTunnelLocalPortNumber = FSessionData->TunnelLocalPortNumber;
|
|
if (FTunnelLocalPortNumber == 0)
|
|
{
|
|
// Randomizing the port selection to reduce a chance of conflicts.
|
|
std::vector<int> Ports;
|
|
for (int Port = Configuration->TunnelLocalPortNumberLow; Port <= Configuration->TunnelLocalPortNumberHigh; Port++)
|
|
{
|
|
Ports.push_back(Port);
|
|
}
|
|
|
|
do
|
|
{
|
|
if (Ports.empty())
|
|
{
|
|
FatalError(NULL, FMTLOAD(TUNNEL_NO_FREE_PORT,
|
|
(Configuration->TunnelLocalPortNumberLow, Configuration->TunnelLocalPortNumberHigh)));
|
|
}
|
|
int Index = Random(SizeToIntChecked(Ports.size()));
|
|
int Port = Ports[Index];
|
|
Ports.erase(Ports.begin() + Index);
|
|
if (IsListenerFree(Port))
|
|
{
|
|
FTunnelLocalPortNumber = Port;
|
|
LogEvent(FORMAT(L"Autoselected tunnel local port number %d", (FTunnelLocalPortNumber)));
|
|
}
|
|
}
|
|
while (FTunnelLocalPortNumber == 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
FTunnelData = FSessionData->CreateTunnelData(FTunnelLocalPortNumber);
|
|
|
|
// The Started argument is not used with Parent being set
|
|
FTunnelLog = new TSessionLog(FTerminalUI, TDateTime(), FTunnelData, Configuration);
|
|
FTunnelLog->SetParent(FLog, L"Tunnel");
|
|
FTunnelLog->ReflectSettings();
|
|
FTunnelUI = new TTunnelUI(this);
|
|
FTunnel = new TSecureShell(FTunnelUI, FTunnelData, FTunnelLog, Configuration);
|
|
|
|
FTunnelOpening = true;
|
|
try
|
|
{
|
|
FTunnel->Open();
|
|
}
|
|
__finally
|
|
{
|
|
FTunnelOpening = false;
|
|
}
|
|
|
|
// When forwarding fails due to a local problem (e.g. port in use), we get the error already here, so abort asap.
|
|
// If we won't abort, particularly in case of "port in use", we may not even detect the problem,
|
|
// as the connection to the port may succeed.
|
|
// Remote-side tunnel problems are handled in TTerminal::Open().
|
|
if (!FTunnel->LastTunnelError.IsEmpty())
|
|
{
|
|
// Throw and handle with any other theoretical forwarding errors that may cause tunnel connection close
|
|
// (probably cannot happen)
|
|
EXCEPTION;
|
|
}
|
|
|
|
FTunnelThread = new TTunnelThread(FTunnel);
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
LogEvent(L"Error opening tunnel.");
|
|
CloseTunnel();
|
|
if (!FTunnelError.IsEmpty())
|
|
{
|
|
FatalError(&E, FMTLOAD(TUNNEL_ERROR, (FTunnelError)));
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CloseTunnel()
|
|
{
|
|
SAFE_DESTROY_EX(TTunnelThread, FTunnelThread);
|
|
FTunnelError = FTunnel->LastTunnelError;
|
|
SAFE_DESTROY_EX(TSecureShell, FTunnel);
|
|
SAFE_DESTROY_EX(TTunnelUI, FTunnelUI);
|
|
SAFE_DESTROY_EX(TSessionLog, FTunnelLog);
|
|
SAFE_DESTROY(FTunnelData);
|
|
|
|
FTunnelLocalPortNumber = 0;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Closed()
|
|
{
|
|
if (FTunnel != NULL)
|
|
{
|
|
CloseTunnel();
|
|
}
|
|
|
|
if (OnClose)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnClose(this);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
FStatus = ssClosed;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ProcessGUI()
|
|
{
|
|
// Do not process GUI here, as we are called directly from a GUI loop and may
|
|
// recurse for good.
|
|
// Alternatively we may check for (FOperationProgress == NULL)
|
|
if (FNesting == 0)
|
|
{
|
|
TAutoNestingCounter NestingCounter(FNesting);
|
|
::ProcessGUI();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Progress(TFileOperationProgressType * OperationProgress)
|
|
{
|
|
if (FNesting == 0)
|
|
{
|
|
TAutoNestingCounter NestingCounter(FNesting);
|
|
OperationProgress->Progress();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Reopen(int Params)
|
|
{
|
|
TFSProtocol OrigFSProtocol = SessionData->FSProtocol;
|
|
UnicodeString PrevRemoteDirectory = SessionData->RemoteDirectory;
|
|
bool PrevReadCurrentDirectoryPending = FReadCurrentDirectoryPending;
|
|
bool PrevReadDirectoryPending = FReadDirectoryPending;
|
|
DebugAssert(!FSuspendTransaction);
|
|
bool PrevAutoReadDirectory = FAutoReadDirectory;
|
|
// here used to be a check for FExceptionOnFail being 0
|
|
// but it can happen, e.g. when we are downloading file to execute it.
|
|
// however I'm not sure why we mind having exception-on-fail enabled here
|
|
int PrevExceptionOnFail = FExceptionOnFail;
|
|
try
|
|
{
|
|
FReadCurrentDirectoryPending = false;
|
|
FReadDirectoryPending = false;
|
|
// Not sure why we are suspending the transaction in the first place,
|
|
// but definitely when set while connecting auto loaded workspace session, it causes loading the directory twice.
|
|
// (when reconnecting lost connection, it's usually prevented by cached directory)
|
|
// Preventing that by suspending transaction only when there is one.
|
|
FSuspendTransaction = (FInTransaction > 0);
|
|
FExceptionOnFail = 0;
|
|
// typically, we avoid reading directory, when there is operation ongoing,
|
|
// for file list which may reference files from current directory
|
|
if (FLAGSET(Params, ropNoReadDirectory))
|
|
{
|
|
AutoReadDirectory = false;
|
|
}
|
|
|
|
// only peek, we may not be connected at all atm,
|
|
// so make sure we do not try retrieving current directory from the server
|
|
// (particularly with FTP)
|
|
UnicodeString ACurrentDirectory = PeekCurrentDirectory();
|
|
if (!ACurrentDirectory.IsEmpty())
|
|
{
|
|
SessionData->RemoteDirectory = ACurrentDirectory;
|
|
}
|
|
if (SessionData->FSProtocol == fsSFTP)
|
|
{
|
|
SessionData->FSProtocol = (FFSProtocol == cfsSCP ? fsSCPonly : fsSFTPonly);
|
|
}
|
|
|
|
Open();
|
|
}
|
|
__finally
|
|
{
|
|
SessionData->RemoteDirectory = PrevRemoteDirectory;
|
|
SessionData->FSProtocol = OrigFSProtocol;
|
|
FAutoReadDirectory = PrevAutoReadDirectory;
|
|
FReadCurrentDirectoryPending = PrevReadCurrentDirectoryPending;
|
|
FReadDirectoryPending = PrevReadDirectoryPending;
|
|
FSuspendTransaction = false;
|
|
FExceptionOnFail = PrevExceptionOnFail;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind,
|
|
UnicodeString Name, UnicodeString Instructions, UnicodeString Prompt, bool Echo, int MaxLen, UnicodeString & Result)
|
|
{
|
|
bool AResult;
|
|
TStrings * Prompts = new TStringList;
|
|
TStrings * Results = new TStringList;
|
|
try
|
|
{
|
|
Prompts->AddObject(Prompt, reinterpret_cast<TObject *>(FLAGMASK(Echo, pupEcho)));
|
|
Results->AddObject(Result, reinterpret_cast<TObject *>(MaxLen));
|
|
|
|
AResult = PromptUser(Data, Kind, Name, Instructions, Prompts, Results);
|
|
|
|
Result = Results->Strings[0];
|
|
}
|
|
__finally
|
|
{
|
|
delete Prompts;
|
|
delete Results;
|
|
}
|
|
|
|
return AResult;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind,
|
|
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
|
|
{
|
|
// If PromptUser is overridden in descendant class, the overridden version
|
|
// is not called when accessed via TSessionIU interface.
|
|
// So this is workaround.
|
|
// Actually no longer needed as we do not override DoPromptUser
|
|
// anymore in TSecondaryTerminal.
|
|
return DoPromptUser(Data, Kind, Name, Instructions, Prompts, Results);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminal::GetPrimaryTerminal()
|
|
{
|
|
return this;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoPromptUser(TSessionData * /*Data*/, TPromptKind Kind,
|
|
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
|
|
{
|
|
bool AResult = false;
|
|
|
|
|
|
bool PasswordOrPassphrasePrompt = IsPasswordOrPassphrasePrompt(Kind, Prompts);
|
|
if (PasswordOrPassphrasePrompt)
|
|
{
|
|
bool & PasswordTried =
|
|
FTunnelOpening ? FRememberedTunnelPasswordTried : FRememberedPasswordTried;
|
|
if (!PasswordTried)
|
|
{
|
|
// let's expect that the main session is already authenticated and its password
|
|
// is not written after, so no locking is necessary
|
|
// (no longer true, once the main session can be reconnected)
|
|
UnicodeString APassword;
|
|
if (FTunnelOpening)
|
|
{
|
|
APassword = PrimaryTerminal->GetRememberedTunnelPassword();
|
|
}
|
|
else
|
|
{
|
|
APassword = PrimaryTerminal->GetRememberedPassword();
|
|
}
|
|
FRememberedPasswordUsed = true;
|
|
Results->Strings[0] = APassword;
|
|
if (!Results->Strings[0].IsEmpty())
|
|
{
|
|
LogEvent(L"Using remembered password.");
|
|
AResult = true;
|
|
}
|
|
PasswordTried = true;
|
|
}
|
|
}
|
|
|
|
if (!AResult)
|
|
{
|
|
if (PasswordOrPassphrasePrompt && !Configuration->RememberPassword)
|
|
{
|
|
Prompts->Objects[0] = reinterpret_cast<TObject*>(reinterpret_cast<int>(Prompts->Objects[0]) | pupRemember);
|
|
}
|
|
|
|
if (OnPromptUser != NULL)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnPromptUser(this, Kind, Name, Instructions, Prompts, Results, AResult, NULL);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AResult && PasswordOrPassphrasePrompt &&
|
|
(Configuration->RememberPassword || FLAGSET(int(Prompts->Objects[0]), pupRemember)))
|
|
{
|
|
UnicodeString Password = DenormalizeString(Results->Strings[0]);
|
|
RawByteString EncryptedPassword = EncryptPassword(Password);
|
|
Shred(Password);
|
|
if (FTunnelOpening)
|
|
{
|
|
PrimaryTerminal->FRememberedTunnelPassword = EncryptedPassword;
|
|
}
|
|
else
|
|
{
|
|
PrimaryTerminal->FRememberedPassword = EncryptedPassword;
|
|
PrimaryTerminal->FRememberedPasswordKind = Kind;
|
|
}
|
|
}
|
|
}
|
|
|
|
return AResult;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminal::QueryUser(const UnicodeString Query,
|
|
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
LogEvent(FORMAT(L"Asking user:\n%s (%s)", (Query, (MoreMessages ? MoreMessages->CommaText : UnicodeString() ))));
|
|
unsigned int Answer = AbortAnswer(Answers);
|
|
if (FOnQueryUser)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnQueryUser(this, Query, MoreMessages, Answers, Params, Answer, QueryType, NULL);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
UnicodeString Name;
|
|
if (Answer == qaNeverAskAgain)
|
|
{
|
|
Name = L"NeverAskAgain";
|
|
}
|
|
else
|
|
{
|
|
UnicodeString Caption;
|
|
AnswerNameAndCaption(Answer, Name, Caption);
|
|
}
|
|
LogEvent(FORMAT(L"Answer: %s", (Name)));
|
|
return Answer;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminal::QueryUserException(const UnicodeString Query,
|
|
Exception * E, unsigned int Answers, const TQueryParams * Params,
|
|
TQueryType QueryType)
|
|
{
|
|
unsigned int Result;
|
|
UnicodeString ExMessage;
|
|
if (DebugAlwaysTrue(ExceptionMessageFormatted(E, ExMessage) || !Query.IsEmpty()))
|
|
{
|
|
TStrings * MoreMessages = new TStringList();
|
|
try
|
|
{
|
|
if (!ExMessage.IsEmpty() && !Query.IsEmpty())
|
|
{
|
|
MoreMessages->Add(UnformatMessage(ExMessage));
|
|
}
|
|
|
|
ExtException * EE = dynamic_cast<ExtException*>(E);
|
|
if ((EE != NULL) && (EE->MoreMessages != NULL))
|
|
{
|
|
MoreMessages->AddStrings(EE->MoreMessages);
|
|
}
|
|
|
|
// We know MoreMessages not to be NULL here,
|
|
// AppendExceptionStackTrace should never return true
|
|
// (indicating it had to create the string list)
|
|
DebugAlwaysFalse(AppendExceptionStackTrace(E, MoreMessages));
|
|
|
|
TQueryParams HelpKeywordOverrideParams;
|
|
if (Params != NULL)
|
|
{
|
|
HelpKeywordOverrideParams.Assign(*Params);
|
|
}
|
|
HelpKeywordOverrideParams.HelpKeyword =
|
|
MergeHelpKeyword(HelpKeywordOverrideParams.HelpKeyword, GetExceptionHelpKeyword(E));
|
|
|
|
Result = QueryUser(!Query.IsEmpty() ? Query : ExMessage,
|
|
MoreMessages->Count ? MoreMessages : NULL,
|
|
Answers, &HelpKeywordOverrideParams, QueryType);
|
|
}
|
|
__finally
|
|
{
|
|
delete MoreMessages;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Result = AbortAnswer(Answers);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DisplayBanner(const UnicodeString & Banner)
|
|
{
|
|
if (OnDisplayBanner != NULL)
|
|
{
|
|
unsigned int Params = 0; // shut up
|
|
if (Configuration->ShowBanner(SessionData->SessionKey, Banner, Params) ||
|
|
Configuration->ForceBanners)
|
|
{
|
|
bool NeverShowAgain = false;
|
|
int Options =
|
|
FLAGMASK(Configuration->ForceBanners, boDisableNeverShowAgain);
|
|
unsigned int OrigParams = Params;
|
|
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnDisplayBanner(this, SessionData->SessionName, Banner, NeverShowAgain, Options, Params);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
if (!Configuration->ForceBanners && NeverShowAgain)
|
|
{
|
|
Configuration->NeverShowBanner(SessionData->SessionKey, Banner);
|
|
}
|
|
if (OrigParams != Params)
|
|
{
|
|
Configuration->SetBannerParams(SessionData->SessionKey, Params);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::HandleExtendedException(Exception * E)
|
|
{
|
|
Log->AddException(E);
|
|
if (OnShowExtendedException != NULL)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
// the event handler may destroy 'this' ...
|
|
OnShowExtendedException(this, E, NULL);
|
|
// .. hence guard is dismissed from destructor, to make following call no-op
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ShowExtendedException(Exception * E)
|
|
{
|
|
Log->AddException(E);
|
|
if (OnShowExtendedException != NULL)
|
|
{
|
|
OnShowExtendedException(this, E, NULL);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoInformation(
|
|
const UnicodeString & Str, int Phase, const UnicodeString & Additional)
|
|
{
|
|
if (OnInformation)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnInformation(this, Str, Phase, Additional);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Information(const UnicodeString & Str)
|
|
{
|
|
DoInformation(Str);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoProgress(TFileOperationProgressType & ProgressData)
|
|
{
|
|
|
|
if ((Configuration->ActualLogProtocol >= 1) &&
|
|
ProgressData.IsTransfer())
|
|
{
|
|
DWORD Now = GetTickCount();
|
|
if (Now - FLastProgressLogged >= 1000)
|
|
{
|
|
LogEvent(L"Transfer progress: " + ProgressData.GetLogStr(false));
|
|
FLastProgressLogged = Now;
|
|
}
|
|
}
|
|
|
|
if (ProgressData.TransferredSize > 0)
|
|
{
|
|
FFileTransferAny = true;
|
|
}
|
|
|
|
if (OnProgress != NULL)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnProgress(ProgressData);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoFinished(TFileOperation Operation, TOperationSide Side, bool Temp,
|
|
const UnicodeString & FileName, bool Success, bool NotCancelled, TOnceDoneOperation & OnceDoneOperation)
|
|
{
|
|
if (OnFinished != NULL)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
OnFinished(Operation, Side, Temp, FileName, Success, NotCancelled, OnceDoneOperation);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SaveCapabilities(TFileSystemInfo & FileSystemInfo)
|
|
{
|
|
for (int Index = 0; Index < fcCount; Index++)
|
|
{
|
|
FileSystemInfo.IsCapable[Index] = IsCapable[static_cast<TFSCapability>(Index)];
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetIsCapable(TFSCapability Capability) const
|
|
{
|
|
if (DebugAlwaysTrue(FFileSystem != NULL))
|
|
{
|
|
switch (Capability)
|
|
{
|
|
case fcBackgroundTransfers:
|
|
return !IsEncryptingFiles();
|
|
|
|
default:
|
|
return FFileSystem->IsCapable(Capability);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::AbsolutePath(UnicodeString Path, bool Local)
|
|
{
|
|
return FFileSystem->AbsolutePath(Path, Local);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReactOnCommand(int /*TFSCommand*/ Cmd)
|
|
{
|
|
bool ChangesDirectory = false;
|
|
bool ModifiesFiles = false;
|
|
|
|
switch (static_cast<TFSCommand>(Cmd)) {
|
|
case fsChangeDirectory:
|
|
case fsHomeDirectory:
|
|
ChangesDirectory = true;
|
|
break;
|
|
|
|
case fsCopyToRemote:
|
|
case fsDeleteFile:
|
|
case fsRenameFile:
|
|
case fsMoveFile:
|
|
case fsCopyFile:
|
|
case fsCreateDirectory:
|
|
case fsChangeMode:
|
|
case fsChangeGroup:
|
|
case fsChangeOwner:
|
|
case fsChangeProperties:
|
|
case fsLock:
|
|
ModifiesFiles = true;
|
|
break;
|
|
|
|
case fsAnyCommand:
|
|
ChangesDirectory = true;
|
|
ModifiesFiles = true;
|
|
break;
|
|
}
|
|
|
|
if (ChangesDirectory)
|
|
{
|
|
if (!InTransaction())
|
|
{
|
|
ReadCurrentDirectory();
|
|
if (AutoReadDirectory)
|
|
{
|
|
ReadDirectory(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FReadCurrentDirectoryPending = true;
|
|
if (AutoReadDirectory)
|
|
{
|
|
FReadDirectoryPending = true;
|
|
}
|
|
}
|
|
}
|
|
else if (ModifiesFiles && AutoReadDirectory && Configuration->AutoReadDirectoryAfterOp)
|
|
{
|
|
if (!InTransaction())
|
|
{
|
|
ReadDirectory(true);
|
|
}
|
|
else
|
|
{
|
|
FReadDirectoryPending = true;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
NORETURN void __fastcall TTerminal::TerminalError(UnicodeString Msg)
|
|
{
|
|
TerminalError(NULL, Msg);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
NORETURN void __fastcall TTerminal::TerminalError(
|
|
Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
|
|
{
|
|
throw ETerminal(E, Msg, HelpKeyword);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoQueryReopen(Exception * E)
|
|
{
|
|
EFatal * Fatal = dynamic_cast<EFatal *>(E);
|
|
DebugAssert(Fatal != NULL);
|
|
bool Result;
|
|
if ((Fatal != NULL) && Fatal->ReopenQueried)
|
|
{
|
|
Result = false;
|
|
}
|
|
else
|
|
{
|
|
LogEvent(L"Connection was lost, asking what to do.");
|
|
|
|
TQueryParams Params(qpAllowContinueOnError);
|
|
Params.Timeout = Configuration->SessionReopenAuto;
|
|
Params.TimeoutAnswer = qaRetry;
|
|
Params.TimeoutResponse = Params.TimeoutAnswer;
|
|
TQueryButtonAlias Aliases[1];
|
|
Aliases[0].Button = qaRetry;
|
|
Aliases[0].Alias = LoadStr(RECONNECT_BUTTON);
|
|
Params.Aliases = Aliases;
|
|
Params.AliasesCount = std::size(Aliases);
|
|
Result = (QueryUserException(L"", E, qaRetry | qaAbort, &Params, qtError) == qaRetry);
|
|
|
|
if (Fatal != NULL)
|
|
{
|
|
Fatal->ReopenQueried = true;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::ContinueReopen(TDateTime Start)
|
|
{
|
|
return
|
|
(Configuration->SessionReopenTimeout == 0) ||
|
|
(int(double(Now() - Start) * MSecsPerDay) < Configuration->SessionReopenTimeout);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::QueryReopen(Exception * E, int Params,
|
|
TFileOperationProgressType * OperationProgress)
|
|
{
|
|
TSuspendFileOperationProgress Suspend(OperationProgress);
|
|
|
|
bool Result = DoQueryReopen(E);
|
|
|
|
if (Result)
|
|
{
|
|
TDateTime Start = Now();
|
|
do
|
|
{
|
|
try
|
|
{
|
|
Reopen(Params);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
if (!Active)
|
|
{
|
|
Result =
|
|
ContinueReopen(Start) &&
|
|
DoQueryReopen(&E);
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
while (!Active && Result);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::FileOperationLoopQuery(Exception & E,
|
|
TFileOperationProgressType * OperationProgress, const UnicodeString Message,
|
|
unsigned int Flags, UnicodeString SpecialRetry, UnicodeString HelpKeyword)
|
|
{
|
|
bool Result = false;
|
|
Log->AddException(&E);
|
|
unsigned int Answer;
|
|
bool AllowSkip = FLAGSET(Flags, folAllowSkip);
|
|
bool SkipToAllPossible = AllowSkip && (OperationProgress != NULL);
|
|
|
|
if (SkipToAllPossible && OperationProgress->SkipToAll)
|
|
{
|
|
Answer = qaSkip;
|
|
}
|
|
else
|
|
{
|
|
int Answers = qaRetry | qaAbort |
|
|
FLAGMASK(AllowSkip, qaSkip) |
|
|
FLAGMASK(SkipToAllPossible, qaAll) |
|
|
FLAGMASK(!SpecialRetry.IsEmpty(), qaYes);
|
|
TQueryParams Params(qpAllowContinueOnError | FLAGMASK(!AllowSkip, qpFatalAbort));
|
|
Params.HelpKeyword = HelpKeyword;
|
|
TQueryButtonAlias Aliases[2];
|
|
int AliasCount = 0;
|
|
|
|
if (FLAGSET(Answers, qaAll))
|
|
{
|
|
Aliases[AliasCount].Button = qaAll;
|
|
Aliases[AliasCount].Alias = LoadStr(SKIP_ALL_BUTTON);
|
|
AliasCount++;
|
|
}
|
|
if (FLAGSET(Answers, qaYes))
|
|
{
|
|
Aliases[AliasCount].Button = qaYes;
|
|
Aliases[AliasCount].Alias = SpecialRetry;
|
|
AliasCount++;
|
|
}
|
|
|
|
if (AliasCount > 0)
|
|
{
|
|
Params.Aliases = Aliases;
|
|
Params.AliasesCount = AliasCount;
|
|
}
|
|
|
|
{
|
|
TSuspendFileOperationProgress Suspend(OperationProgress);
|
|
Answer = QueryUserException(Message, &E, Answers, &Params, qtError);
|
|
}
|
|
|
|
if (Answer == qaAll)
|
|
{
|
|
DebugAssert(OperationProgress != NULL);
|
|
OperationProgress->SetSkipToAll();
|
|
Answer = qaSkip;
|
|
}
|
|
if (Answer == qaYes)
|
|
{
|
|
Result = true;
|
|
Answer = qaRetry;
|
|
}
|
|
}
|
|
|
|
if (Answer != qaRetry)
|
|
{
|
|
if ((Answer == qaAbort) && (OperationProgress != NULL))
|
|
{
|
|
OperationProgress->SetCancel(csCancel);
|
|
}
|
|
|
|
if (AllowSkip)
|
|
{
|
|
throw ESkipFile(&E, Message);
|
|
}
|
|
else
|
|
{
|
|
// this can happen only during file transfer with SCP
|
|
throw ExtException(&E, Message);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FileOperationLoopEnd(Exception & E,
|
|
TFileOperationProgressType * OperationProgress, const UnicodeString & Message,
|
|
unsigned int Flags, const UnicodeString & SpecialRetry, const UnicodeString & HelpKeyword)
|
|
{
|
|
if ((dynamic_cast<EAbort *>(&E) != NULL) ||
|
|
(dynamic_cast<ESkipFile *>(&E) != NULL) ||
|
|
(dynamic_cast<ESkipFile *>(&E) != NULL))
|
|
{
|
|
RethrowException(&E);
|
|
}
|
|
else if (dynamic_cast<EFatal *>(&E) != NULL)
|
|
{
|
|
if (FLAGCLEAR(Flags, folRetryOnFatal))
|
|
{
|
|
RethrowException(&E);
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(SpecialRetry.IsEmpty());
|
|
std::unique_ptr<Exception> E2(new EFatal(&E, Message, HelpKeyword));
|
|
if (!DoQueryReopen(E2.get()))
|
|
{
|
|
RethrowException(E2.get());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FileOperationLoopQuery(E, OperationProgress, Message, Flags, SpecialRetry, HelpKeyword);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int __fastcall TTerminal::FileOperationLoop(TFileOperationEvent CallBackFunc,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags,
|
|
const UnicodeString Message, void * Param1, void * Param2)
|
|
{
|
|
DebugAssert(CallBackFunc);
|
|
int Result;
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
Result = CallBackFunc(Param1, Param2);
|
|
}
|
|
FILE_OPERATION_LOOP_END_EX(Message, Flags);
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ClearCaches()
|
|
{
|
|
FDirectoryCache->Clear();
|
|
if (FDirectoryChangesCache != NULL)
|
|
{
|
|
FDirectoryChangesCache->Clear();
|
|
}
|
|
if (FCommandSession != NULL)
|
|
{
|
|
FCommandSession->ClearCaches();
|
|
}
|
|
FFileSystem->ClearCaches();
|
|
FEncryptedFileNames.clear();
|
|
FFoldersScannedForEncryptedFiles.clear();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ClearCachedFileList(const UnicodeString Path,
|
|
bool SubDirs)
|
|
{
|
|
FDirectoryCache->ClearFileList(Path, SubDirs);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::AddCachedFileList(TRemoteFileList * FileList)
|
|
{
|
|
FDirectoryCache->AddFileList(FileList);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFileList * __fastcall TTerminal::DirectoryFileList(const UnicodeString Path, TDateTime Timestamp, bool CanLoad)
|
|
{
|
|
TRemoteFileList * Result = NULL;
|
|
if (UnixSamePath(FFiles->Directory, Path))
|
|
{
|
|
if (Timestamp < FFiles->Timestamp)
|
|
{
|
|
Result = new TRemoteFileList();
|
|
FFiles->DuplicateTo(Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FDirectoryCache->HasNewerFileList(Path, Timestamp))
|
|
{
|
|
Result = new TRemoteFileList();
|
|
DebugAlwaysTrue(FDirectoryCache->GetFileList(Path, Result));
|
|
}
|
|
// do not attempt to load file list if there is cached version,
|
|
// only absence of cached version indicates that we consider
|
|
// the directory content obsolete
|
|
else if (CanLoad && !FDirectoryCache->HasFileList(Path))
|
|
{
|
|
Result = new TRemoteFileList();
|
|
Result->Directory = Path;
|
|
|
|
try
|
|
{
|
|
ReadDirectory(Result);
|
|
}
|
|
catch(...)
|
|
{
|
|
SAFE_DESTROY(Result);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SetCurrentDirectory(UnicodeString value)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
if (value != FFileSystem->CurrentDirectory)
|
|
{
|
|
ChangeDirectory(value);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetCurrentDirectory()
|
|
{
|
|
if (FFileSystem != NULL)
|
|
{
|
|
// there's occasional crash when assigning FFileSystem->CurrentDirectory
|
|
// to FCurrentDirectory, splitting the assignment to two statements
|
|
// to locate the crash more closely
|
|
UnicodeString CurrentDirectory = FFileSystem->CurrentDirectory;
|
|
FCurrentDirectory = CurrentDirectory;
|
|
if (FCurrentDirectory.IsEmpty())
|
|
{
|
|
ReadCurrentDirectory();
|
|
}
|
|
}
|
|
|
|
return FCurrentDirectory;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::PeekCurrentDirectory()
|
|
{
|
|
if (FFileSystem)
|
|
{
|
|
FCurrentDirectory = FFileSystem->CurrentDirectory;
|
|
}
|
|
|
|
return FCurrentDirectory;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
const TRemoteTokenList * __fastcall TTerminal::GetGroups()
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
LookupUsersGroups();
|
|
return &FGroups;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
const TRemoteTokenList * __fastcall TTerminal::GetUsers()
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
LookupUsersGroups();
|
|
return &FUsers;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
const TRemoteTokenList * __fastcall TTerminal::GetMembership()
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
LookupUsersGroups();
|
|
return &FMembership;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetUserName() const
|
|
{
|
|
// in future might also be implemented to detect username similar to GetUserGroups
|
|
DebugAssert(FFileSystem != NULL);
|
|
UnicodeString Result = FFileSystem->GetUserName();
|
|
// Is empty also when stored username was used
|
|
if (Result.IsEmpty())
|
|
{
|
|
Result = SessionData->UserNameExpanded;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetAreCachesEmpty() const
|
|
{
|
|
return FDirectoryCache->IsEmpty &&
|
|
((FDirectoryChangesCache == NULL) || FDirectoryChangesCache->IsEmpty);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoInitializeLog()
|
|
{
|
|
if (FOnInitializeLog)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnInitializeLog(this);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoChangeDirectory()
|
|
{
|
|
if (FOnChangeDirectory)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnChangeDirectory(this);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoReadDirectory(bool ReloadOnly)
|
|
{
|
|
if (FOnReadDirectory)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnReadDirectory(this, ReloadOnly);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoStartReadDirectory()
|
|
{
|
|
if (FOnStartReadDirectory)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnStartReadDirectory(this);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoReadDirectoryProgress(int Progress, int ResolvedLinks, bool & Cancel)
|
|
{
|
|
if (FReadingCurrentDirectory && (FOnReadDirectoryProgress != NULL))
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnReadDirectoryProgress(this, Progress, ResolvedLinks, Cancel);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
if (FOnFindingFile != NULL)
|
|
{
|
|
TCallbackGuard Guard(this);
|
|
try
|
|
{
|
|
FOnFindingFile(this, L"", Cancel);
|
|
Guard.Verify();
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!Guard.Verify(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::InTransaction()
|
|
{
|
|
return (FInTransaction > 0) && !FSuspendTransaction;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::BeginTransaction()
|
|
{
|
|
if (FInTransaction == 0)
|
|
{
|
|
FReadCurrentDirectoryPending = false;
|
|
FReadDirectoryPending = false;
|
|
}
|
|
FInTransaction++;
|
|
|
|
if (FCommandSession != NULL)
|
|
{
|
|
FCommandSession->BeginTransaction();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::EndTransaction()
|
|
{
|
|
DoEndTransaction(false);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoEndTransaction(bool Inform)
|
|
{
|
|
if (FInTransaction == 0)
|
|
TerminalError(L"Can't end transaction, not in transaction");
|
|
DebugAssert(FInTransaction > 0);
|
|
FInTransaction--;
|
|
|
|
// it connection was closed due to fatal error during transaction, do nothing
|
|
if (Active)
|
|
{
|
|
if (FInTransaction == 0)
|
|
{
|
|
try
|
|
{
|
|
if (FReadCurrentDirectoryPending)
|
|
{
|
|
ReadCurrentDirectory();
|
|
}
|
|
|
|
if (FReadDirectoryPending)
|
|
{
|
|
if (Inform)
|
|
{
|
|
DoInformation(LoadStr(STATUS_OPEN_DIRECTORY), -1, CurrentDirectory);
|
|
}
|
|
ReadDirectory(!FReadCurrentDirectoryPending);
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
FReadCurrentDirectoryPending = false;
|
|
FReadDirectoryPending = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FCommandSession != NULL)
|
|
{
|
|
FCommandSession->EndTransaction();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SetExceptionOnFail(bool value)
|
|
{
|
|
if (value)
|
|
{
|
|
FExceptionOnFail++;
|
|
}
|
|
else
|
|
{
|
|
if (FExceptionOnFail == 0)
|
|
throw Exception(L"ExceptionOnFail is already zero.");
|
|
FExceptionOnFail--;
|
|
}
|
|
|
|
if (FCommandSession != NULL)
|
|
{
|
|
FCommandSession->FExceptionOnFail = FExceptionOnFail;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetExceptionOnFail() const
|
|
{
|
|
return (FExceptionOnFail > 0);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FatalAbort()
|
|
{
|
|
FatalError(NULL, "");
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
|
|
{
|
|
bool SecureShellActive = (FSecureShell != NULL) && FSecureShell->Active;
|
|
if (Active || SecureShellActive)
|
|
{
|
|
// We log this instead of exception handler, because Close() would
|
|
// probably cause exception handler to loose pointer to TShellLog()
|
|
LogEvent(L"Attempt to close connection due to fatal exception:");
|
|
Log->Add(llException, Msg);
|
|
Log->AddException(E);
|
|
|
|
if (Active)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
// this may happen if failure of authentication of SSH, owned by terminal yet
|
|
// (because the protocol was not decided yet), is detected by us (not by putty).
|
|
// e.g. not verified host key
|
|
if (SecureShellActive)
|
|
{
|
|
FSecureShell->Close();
|
|
}
|
|
}
|
|
|
|
if (FCallbackGuard != NULL)
|
|
{
|
|
FCallbackGuard->FatalError(E, Msg, HelpKeyword);
|
|
}
|
|
else
|
|
{
|
|
throw ESshFatal(E, Msg, HelpKeyword);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CommandError(Exception * E, const UnicodeString Msg)
|
|
{
|
|
CommandError(E, Msg, 0);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminal::CommandError(Exception * E, const UnicodeString Msg,
|
|
unsigned int Answers, const UnicodeString HelpKeyword)
|
|
{
|
|
// may not be, particularly when TTerminal::Reopen is being called
|
|
// from within OnShowExtendedException handler
|
|
DebugAssert(FCallbackGuard == NULL);
|
|
unsigned int Result = 0;
|
|
if (E && E->InheritsFrom(__classid(EFatal)))
|
|
{
|
|
FatalError(E, Msg, HelpKeyword);
|
|
}
|
|
else if (E && E->InheritsFrom(__classid(EAbort)))
|
|
{
|
|
// resent EAbort exception
|
|
Abort();
|
|
}
|
|
else if (ExceptionOnFail)
|
|
{
|
|
throw ECommand(E, Msg, HelpKeyword);
|
|
}
|
|
else if (!Answers)
|
|
{
|
|
ECommand * ECmd = new ECommand(E, Msg, HelpKeyword);
|
|
try
|
|
{
|
|
HandleExtendedException(ECmd);
|
|
}
|
|
__finally
|
|
{
|
|
delete ECmd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// small hack to enable "skip to all" for TRetryOperationLoop
|
|
bool CanSkip = FLAGSET(Answers, qaSkip) && (OperationProgress != NULL);
|
|
if (CanSkip && OperationProgress->SkipToAll)
|
|
{
|
|
Result = qaSkip;
|
|
}
|
|
else
|
|
{
|
|
TQueryParams Params(qpAllowContinueOnError, HelpKeyword);
|
|
TQueryButtonAlias Aliases[1];
|
|
if (CanSkip)
|
|
{
|
|
Aliases[0].Button = qaAll;
|
|
Aliases[0].Alias = LoadStr(SKIP_ALL_BUTTON);
|
|
Params.Aliases = Aliases;
|
|
Params.AliasesCount = std::size(Aliases);
|
|
Answers |= qaAll;
|
|
}
|
|
Result = QueryUserException(Msg, E, Answers, &Params, qtError);
|
|
if (Result == qaAll)
|
|
{
|
|
DebugAssert(OperationProgress != NULL);
|
|
OperationProgress->SetSkipToAll();
|
|
Result = qaSkip;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::HandleException(Exception * E)
|
|
{
|
|
if (ExceptionOnFail)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Log->AddException(E);
|
|
return true;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CloseOnCompletion(
|
|
TOnceDoneOperation Operation, const UnicodeString & Message,
|
|
const UnicodeString & TargetLocalPath, const UnicodeString & DestLocalFileName)
|
|
{
|
|
if (FOperationProgressPersistence != NULL)
|
|
{
|
|
DebugAssert(Message.IsEmpty());
|
|
FOperationProgressOnceDoneOperation = Operation;
|
|
}
|
|
else
|
|
{
|
|
Configuration->Usage->Inc(L"ClosesOnCompletion");
|
|
LogEvent(L"Closing session after completed operation (as requested by user)");
|
|
Close();
|
|
throw ESshTerminate(NULL,
|
|
MainInstructions(Message.IsEmpty() ? LoadStr(CLOSED_ON_COMPLETION) : Message),
|
|
Operation, TargetLocalPath, DestLocalFileName);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TBatchOverwrite __fastcall TTerminal::EffectiveBatchOverwrite(
|
|
const UnicodeString & SourceFullFileName, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, bool Special)
|
|
{
|
|
TBatchOverwrite Result;
|
|
if (Special &&
|
|
(FLAGSET(Params, cpResume) || CopyParam->ResumeTransfer(SourceFullFileName)) &&
|
|
(CopyParam->PartOffset < 0))
|
|
{
|
|
Result = boResume;
|
|
}
|
|
else if (FLAGSET(Params, cpAppend))
|
|
{
|
|
Result = boAppend;
|
|
}
|
|
else if (CopyParam->NewerOnly &&
|
|
(((OperationProgress->Side == osLocal) && IsCapable[fcNewerOnlyUpload]) ||
|
|
(OperationProgress->Side != osLocal)) &&
|
|
DebugAlwaysTrue(CopyParam->PartOffset < 0))
|
|
{
|
|
// no way to change batch overwrite mode when CopyParam->NewerOnly is on
|
|
Result = boOlder;
|
|
}
|
|
else if (FLAGSET(Params, cpNoConfirmation) || !Configuration->ConfirmOverwriting)
|
|
{
|
|
// no way to change batch overwrite mode when overwrite confirmations are off
|
|
DebugAssert(OperationProgress->BatchOverwrite == boNo);
|
|
Result = boAll;
|
|
}
|
|
else
|
|
{
|
|
Result = OperationProgress->BatchOverwrite;
|
|
if (!Special &&
|
|
((Result == boOlder) || (Result == boAlternateResume) || (Result == boResume)))
|
|
{
|
|
Result = boNo;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CheckRemoteFile(
|
|
const UnicodeString & FileName, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress)
|
|
{
|
|
bool Result;
|
|
OperationProgress->LockUserSelections();
|
|
try
|
|
{
|
|
Result = (EffectiveBatchOverwrite(FileName, CopyParam, Params, OperationProgress, true) != boAll);
|
|
}
|
|
__finally
|
|
{
|
|
OperationProgress->UnlockUserSelections();
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
unsigned int __fastcall TTerminal::ConfirmFileOverwrite(
|
|
const UnicodeString & SourceFullFileName, const UnicodeString & TargetFileName,
|
|
const TOverwriteFileParams * FileParams, unsigned int Answers, TQueryParams * QueryParams,
|
|
TOperationSide Side, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
|
|
UnicodeString Message)
|
|
{
|
|
unsigned int Result = qaCancel; // shut up
|
|
TBatchOverwrite BatchOverwrite;
|
|
bool CanAlternateResume =
|
|
(FileParams != NULL) &&
|
|
(FileParams->DestSize < FileParams->SourceSize) &&
|
|
!OperationProgress->AsciiTransfer;
|
|
|
|
OperationProgress->LockUserSelections();
|
|
try
|
|
{
|
|
// duplicated in TSFTPFileSystem::SFTPConfirmOverwrite
|
|
BatchOverwrite = EffectiveBatchOverwrite(SourceFullFileName, CopyParam, Params, OperationProgress, true);
|
|
bool Applicable = true;
|
|
switch (BatchOverwrite)
|
|
{
|
|
case boOlder:
|
|
Applicable = (FileParams != NULL);
|
|
break;
|
|
|
|
case boAlternateResume:
|
|
Applicable = CanAlternateResume;
|
|
break;
|
|
|
|
case boResume:
|
|
Applicable = CanAlternateResume;
|
|
break;
|
|
}
|
|
|
|
if (!Applicable)
|
|
{
|
|
TBatchOverwrite ABatchOverwrite = EffectiveBatchOverwrite(SourceFullFileName, CopyParam, Params, OperationProgress, false);
|
|
DebugAssert(BatchOverwrite != ABatchOverwrite);
|
|
BatchOverwrite = ABatchOverwrite;
|
|
}
|
|
|
|
if (BatchOverwrite != boNo)
|
|
{
|
|
LogEvent(1, FORMAT(L"Batch operation mode [%d] is effective", (int(BatchOverwrite))));
|
|
}
|
|
else
|
|
{
|
|
// particularly with parallel transfers, the overall operation can be already cancelled by other parallel operation
|
|
if (OperationProgress->Cancel > csContinue)
|
|
{
|
|
LogEvent(1, L"Transfer cancelled in parallel operation");
|
|
Result = qaCancel;
|
|
}
|
|
else
|
|
{
|
|
if (Message.IsEmpty())
|
|
{
|
|
// Side refers to destination side here
|
|
Message = FMTLOAD((Side == osLocal ? LOCAL_FILE_OVERWRITE2 :
|
|
REMOTE_FILE_OVERWRITE2), (TargetFileName, TargetFileName));
|
|
}
|
|
if (FileParams != NULL)
|
|
{
|
|
Message = FMTLOAD(FILE_OVERWRITE_DETAILS, (Message,
|
|
FormatSize(FileParams->SourceSize),
|
|
DefaultStr(UserModificationStr(FileParams->SourceTimestamp, FileParams->SourcePrecision), LoadStr(TIME_UNKNOWN)),
|
|
FormatSize(FileParams->DestSize),
|
|
DefaultStr(UserModificationStr(FileParams->DestTimestamp, FileParams->DestPrecision), LoadStr(TIME_UNKNOWN))));
|
|
}
|
|
if (DebugAlwaysTrue(QueryParams->HelpKeyword.IsEmpty()))
|
|
{
|
|
QueryParams->HelpKeyword = HELP_OVERWRITE;
|
|
}
|
|
Result = QueryUser(Message, NULL, Answers, QueryParams);
|
|
switch (Result)
|
|
{
|
|
case qaNeverAskAgain:
|
|
Configuration->ConfirmOverwriting = false;
|
|
Result = qaYes;
|
|
break;
|
|
|
|
case qaYesToAll:
|
|
BatchOverwrite = boAll;
|
|
break;
|
|
|
|
case qaAll:
|
|
BatchOverwrite = boOlder;
|
|
break;
|
|
|
|
case qaNoToAll:
|
|
BatchOverwrite = boNone;
|
|
break;
|
|
}
|
|
|
|
// if user has not selected another batch overwrite mode,
|
|
// keep the current one. note that we may get here even
|
|
// when batch overwrite was selected already, but it could not be applied
|
|
// to current transfer (see condition above)
|
|
if (BatchOverwrite != boNo)
|
|
{
|
|
OperationProgress->SetBatchOverwrite(BatchOverwrite);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
OperationProgress->UnlockUserSelections();
|
|
}
|
|
|
|
if (BatchOverwrite != boNo)
|
|
{
|
|
switch (BatchOverwrite)
|
|
{
|
|
case boAll:
|
|
LogEvent(1, L"Overwriting all files");
|
|
Result = qaYes;
|
|
break;
|
|
|
|
case boNone:
|
|
LogEvent(1, L"Not overwriting any file");
|
|
Result = qaNo;
|
|
break;
|
|
|
|
case boOlder:
|
|
if (FileParams == NULL)
|
|
{
|
|
LogEvent(1, L"Not overwriting due to lack of file information");
|
|
Result = qaNo;
|
|
}
|
|
else
|
|
{
|
|
TModificationFmt Precision = LessDateTimePrecision(FileParams->SourcePrecision, FileParams->DestPrecision);
|
|
TDateTime ReducedSourceTimestamp =
|
|
ReduceDateTimePrecision(FileParams->SourceTimestamp, Precision);
|
|
TDateTime ReducedDestTimestamp =
|
|
ReduceDateTimePrecision(FileParams->DestTimestamp, Precision);
|
|
|
|
Result =
|
|
(CompareFileTime(ReducedSourceTimestamp, ReducedDestTimestamp) > 0) ?
|
|
qaYes : qaNo;
|
|
|
|
LogEvent(FORMAT(L"Source file timestamp is [%s], destination timestamp is [%s], will%s overwrite",
|
|
(StandardTimestamp(ReducedSourceTimestamp),
|
|
StandardTimestamp(ReducedDestTimestamp),
|
|
((Result == qaYes) ? L"" : L" not"))));
|
|
}
|
|
break;
|
|
|
|
case boAlternateResume:
|
|
DebugAssert(CanAlternateResume);
|
|
LogEvent(1, L"Alternate resume mode");
|
|
Result = qaSkip; // ugh
|
|
break;
|
|
|
|
case boAppend:
|
|
LogEvent(1, L"Appending file");
|
|
Result = qaRetry;
|
|
break;
|
|
|
|
case boResume:
|
|
LogEvent(1, L"Resuming file transfer");
|
|
Result = qaRetry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FileModified(const TRemoteFile * File,
|
|
const UnicodeString FileName, bool ClearDirectoryChange)
|
|
{
|
|
UnicodeString ParentDirectory;
|
|
UnicodeString Directory;
|
|
|
|
if (SessionData->CacheDirectories || SessionData->CacheDirectoryChanges)
|
|
{
|
|
if ((File != NULL) && (File->Directory != NULL))
|
|
{
|
|
if (File->IsDirectory)
|
|
{
|
|
Directory = File->Directory->FullDirectory + File->FileName;
|
|
}
|
|
ParentDirectory = File->Directory->Directory;
|
|
}
|
|
else if (!FileName.IsEmpty())
|
|
{
|
|
ParentDirectory = UnixExtractFilePath(FileName);
|
|
if (ParentDirectory.IsEmpty())
|
|
{
|
|
ParentDirectory = CurrentDirectory;
|
|
}
|
|
|
|
// this case for scripting
|
|
if ((File != NULL) && File->IsDirectory)
|
|
{
|
|
Directory = UnixIncludeTrailingBackslash(ParentDirectory) +
|
|
UnixExtractFileName(File->FileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SessionData->CacheDirectories)
|
|
{
|
|
if (!Directory.IsEmpty())
|
|
{
|
|
DirectoryModified(Directory, true);
|
|
}
|
|
if (!ParentDirectory.IsEmpty())
|
|
{
|
|
DirectoryModified(ParentDirectory, false);
|
|
}
|
|
}
|
|
|
|
if (SessionData->CacheDirectoryChanges && ClearDirectoryChange)
|
|
{
|
|
if (!Directory.IsEmpty())
|
|
{
|
|
FDirectoryChangesCache->ClearDirectoryChange(Directory);
|
|
FDirectoryChangesCache->ClearDirectoryChangeTarget(Directory);
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DirectoryModified(const UnicodeString Path, bool SubDirs)
|
|
{
|
|
if (Path.IsEmpty())
|
|
{
|
|
ClearCachedFileList(CurrentDirectory, SubDirs);
|
|
}
|
|
else
|
|
{
|
|
ClearCachedFileList(Path, SubDirs);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DirectoryLoaded(TRemoteFileList * FileList)
|
|
{
|
|
AddCachedFileList(FileList);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReloadDirectory()
|
|
{
|
|
if (SessionData->CacheDirectories)
|
|
{
|
|
DirectoryModified(CurrentDirectory, false);
|
|
}
|
|
if (SessionData->CacheDirectoryChanges)
|
|
{
|
|
DebugAssert(FDirectoryChangesCache != NULL);
|
|
FDirectoryChangesCache->ClearDirectoryChange(CurrentDirectory);
|
|
}
|
|
|
|
ReadCurrentDirectory();
|
|
FReadCurrentDirectoryPending = false;
|
|
ReadDirectory(true);
|
|
FReadDirectoryPending = false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::RefreshDirectory()
|
|
{
|
|
if (!SessionData->CacheDirectories)
|
|
{
|
|
LogEvent(L"Not refreshing directory, caching is off.");
|
|
}
|
|
else if (FDirectoryCache->HasNewerFileList(CurrentDirectory, FFiles->Timestamp))
|
|
{
|
|
// Second parameter was added to allow (rather force) using the cache.
|
|
// Before, the directory was reloaded always, it seems useless,
|
|
// has it any reason?
|
|
ReadDirectory(true, true);
|
|
FReadDirectoryPending = false;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::EnsureNonExistence(const UnicodeString FileName)
|
|
{
|
|
// if filename doesn't contain path, we check for existence of file
|
|
if ((UnixExtractFileDir(FileName).IsEmpty()) &&
|
|
UnixSamePath(CurrentDirectory, FFiles->Directory))
|
|
{
|
|
TRemoteFile *File = FFiles->FindFile(FileName);
|
|
if (File)
|
|
{
|
|
if (File->IsDirectory) throw ECommand(NULL, FMTLOAD(RENAME_CREATE_DIR_EXISTS, (FileName)));
|
|
else throw ECommand(NULL, FMTLOAD(RENAME_CREATE_FILE_EXISTS, (FileName)));
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall inline TTerminal::LogEvent(const UnicodeString & Str)
|
|
{
|
|
if (Log->Logging)
|
|
{
|
|
Log->Add(llMessage, Str);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogEvent(int Level, const UnicodeString & Str)
|
|
{
|
|
if (Log->Logging && (Configuration->ActualLogProtocol >= Level))
|
|
{
|
|
Log->Add(llMessage, Str);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::RollbackAction(TSessionAction & Action,
|
|
TFileOperationProgressType * OperationProgress, Exception * E)
|
|
{
|
|
// ESkipFile without "cancel" is file skip,
|
|
// and we do not want to record skipped actions.
|
|
// But ESkipFile with "cancel" is abort and we want to record that.
|
|
// Note that TScpFileSystem modifies the logic of RollbackAction little bit.
|
|
if ((dynamic_cast<ESkipFile *>(E) != NULL) &&
|
|
((OperationProgress == NULL) ||
|
|
(OperationProgress->Cancel == csContinue)))
|
|
{
|
|
Action.Cancel();
|
|
}
|
|
else
|
|
{
|
|
Action.Rollback(E);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoStartup()
|
|
{
|
|
LogEvent(L"Doing startup conversation with host.");
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
Information(LoadStr(STATUS_STARTUP));
|
|
|
|
// Make sure that directory would be loaded at last
|
|
FReadCurrentDirectoryPending = true;
|
|
FReadDirectoryPending = AutoReadDirectory;
|
|
|
|
FFileSystem->DoStartup();
|
|
|
|
LookupUsersGroups();
|
|
|
|
if (!SessionData->RemoteDirectory.IsEmpty())
|
|
{
|
|
if (SessionData->UpdateDirectories)
|
|
{
|
|
ExceptionOnFail = true;
|
|
try
|
|
{
|
|
ChangeDirectory(SessionData->RemoteDirectory);
|
|
}
|
|
catch (...)
|
|
{
|
|
if (!Active || SessionData->RequireDirectories)
|
|
{
|
|
ExceptionOnFail = false;
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
LogEvent(L"Configured initial remote directory cannot be opened, staying in the home directory.");
|
|
}
|
|
}
|
|
ExceptionOnFail = false;
|
|
}
|
|
else
|
|
{
|
|
ChangeDirectory(SessionData->RemoteDirectory);
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
DoEndTransaction(true);
|
|
}
|
|
LogEvent(L"Startup conversation with host finished.");
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReadCurrentDirectory()
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
try
|
|
{
|
|
// reset flag in case we are called externally (like from console dialog)
|
|
FReadCurrentDirectoryPending = false;
|
|
|
|
LogEvent(L"Getting current directory name.");
|
|
UnicodeString OldDirectory = FFileSystem->CurrentDirectory;
|
|
|
|
FFileSystem->ReadCurrentDirectory();
|
|
ReactOnCommand(fsCurrentDirectory);
|
|
|
|
if (SessionData->CacheDirectoryChanges)
|
|
{
|
|
DebugAssert(FDirectoryChangesCache != NULL);
|
|
if (!CurrentDirectory.IsEmpty() && !FLastDirectoryChange.IsEmpty() && (CurrentDirectory != OldDirectory))
|
|
{
|
|
FDirectoryChangesCache->AddDirectoryChange(OldDirectory,
|
|
FLastDirectoryChange, CurrentDirectory);
|
|
}
|
|
// not to breake the cache, if the next directory change would not
|
|
// be initialized by ChangeDirectory(), which sets it
|
|
// (HomeDirectory() particularly)
|
|
FLastDirectoryChange = L"";
|
|
}
|
|
|
|
if (OldDirectory != FFileSystem->CurrentDirectory) DoChangeDirectory();
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, LoadStr(READ_CURRENT_DIR_ERROR));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::DoReadDirectoryFinish(TRemoteDirectory * Files, bool ReloadOnly)
|
|
{
|
|
// Factored out to solve Clang ICE
|
|
TRemoteDirectory * OldFiles = FFiles;
|
|
FFiles = Files;
|
|
try
|
|
{
|
|
DoReadDirectory(ReloadOnly);
|
|
}
|
|
__finally
|
|
{
|
|
// delete only after loading new files to dir view,
|
|
// not to destroy the file objects that the view holds
|
|
// (can be issue in multi threaded environment, such as when the
|
|
// terminal is reconnecting in the terminal thread)
|
|
delete OldFiles;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReadDirectory(bool ReloadOnly, bool ForceCache)
|
|
{
|
|
bool LoadedFromCache = false;
|
|
|
|
if (SessionData->CacheDirectories && FDirectoryCache->HasFileList(CurrentDirectory))
|
|
{
|
|
if (ReloadOnly && !ForceCache)
|
|
{
|
|
LogEvent(L"Cached directory not reloaded.");
|
|
}
|
|
else
|
|
{
|
|
DoStartReadDirectory();
|
|
try
|
|
{
|
|
LoadedFromCache = FDirectoryCache->GetFileList(CurrentDirectory, FFiles);
|
|
}
|
|
__finally
|
|
{
|
|
DoReadDirectory(ReloadOnly);
|
|
}
|
|
|
|
if (LoadedFromCache)
|
|
{
|
|
LogEvent(L"Directory content loaded from cache.");
|
|
}
|
|
else
|
|
{
|
|
LogEvent(L"Cached Directory content has been removed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!LoadedFromCache)
|
|
{
|
|
DoStartReadDirectory();
|
|
FReadingCurrentDirectory = true;
|
|
bool Cancel = false; // dummy
|
|
DoReadDirectoryProgress(0, 0, Cancel);
|
|
|
|
try
|
|
{
|
|
TRemoteDirectory * Files = new TRemoteDirectory(this, FFiles);
|
|
try
|
|
{
|
|
Files->Directory = CurrentDirectory;
|
|
CustomReadDirectory(Files);
|
|
}
|
|
__finally
|
|
{
|
|
DoReadDirectoryProgress(-1, 0, Cancel);
|
|
FReadingCurrentDirectory = false;
|
|
DoReadDirectoryFinish(Files, ReloadOnly);
|
|
if (Active)
|
|
{
|
|
if (SessionData->CacheDirectories)
|
|
{
|
|
DirectoryLoaded(FFiles);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, FmtLoadStr(LIST_DIR_ERROR, ARRAYOFCONST((FFiles->Directory))));
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetRemoteFileInfo(TRemoteFile * File)
|
|
{
|
|
return
|
|
FORMAT(L"%s;%s;%d;%s;%d;%s;%s;%s;%d",
|
|
(File->FileName, File->Type, File->Size, StandardTimestamp(File->Modification), int(File->ModificationFmt),
|
|
File->Owner.LogText, File->Group.LogText, File->Rights->Text,
|
|
File->Attr));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogRemoteFile(TRemoteFile * File)
|
|
{
|
|
// optimization
|
|
if (Log->Logging)
|
|
{
|
|
LogEvent(GetRemoteFileInfo(File));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::FormatFileDetailsForLog(
|
|
const UnicodeString & FileName, TDateTime Modification, __int64 Size, const TRemoteFile * LinkedFile)
|
|
{
|
|
UnicodeString Result;
|
|
// optimization
|
|
if (Log->Logging)
|
|
{
|
|
Result = FORMAT(L"'%s' [%s] [%s]", (FileName, (Modification != TDateTime() ? StandardTimestamp(Modification) : UnicodeString(L"n/a")), IntToStr(Size)));
|
|
if (LinkedFile != NULL)
|
|
{
|
|
LinkedFile = LinkedFile->Resolve(); // in case it's symlink to symlink
|
|
Result += FORMAT(L" - Link to: %s", (FormatFileDetailsForLog(LinkedFile->FileName, LinkedFile->Modification, LinkedFile->Size, NULL)));
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogFileDetails(const UnicodeString & FileName, TDateTime Modification, __int64 Size, const TRemoteFile * LinkedFile)
|
|
{
|
|
// optimization
|
|
if (Log->Logging)
|
|
{
|
|
LogEvent(FORMAT("File: %s", (FormatFileDetailsForLog(FileName, Modification, Size, LinkedFile))));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogFileDone(
|
|
TFileOperationProgressType * OperationProgress, const UnicodeString & DestFileName,
|
|
TTransferSessionAction & Action)
|
|
{
|
|
if (FDestFileName.IsEmpty())
|
|
{
|
|
FDestFileName = DestFileName;
|
|
}
|
|
else
|
|
{
|
|
FMultipleDestinationFiles = true;
|
|
}
|
|
|
|
__int64 Size = OperationProgress->TransferredSize;
|
|
// optimization
|
|
if (Log->Logging)
|
|
{
|
|
LogEvent(FORMAT("Transfer done: '%s' => '%s' [%s]", (OperationProgress->FullFileName, DestFileName, IntToStr(Size))));
|
|
}
|
|
|
|
Action.Size(Size);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CustomReadDirectory(TRemoteFileList * FileList)
|
|
{
|
|
DebugAssert(FileList);
|
|
DebugAssert(FFileSystem);
|
|
|
|
AppLogFmt(L"Reading directory %s", (FileList->Directory));
|
|
|
|
// To match FTP upload/download, we also limit directory listing.
|
|
// For simplicity, we limit it unconditionally, for all protocols for any kind of errors.
|
|
bool FileTransferAny = false;
|
|
TRobustOperationLoop RobustLoop(this, OperationProgress, &FileTransferAny);
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
// Record even an attempt, to avoid listing a directory over and over again, when it is not readable actually
|
|
if (IsEncryptingFiles())
|
|
{
|
|
FFoldersScannedForEncryptedFiles.insert(FileList->Directory);
|
|
}
|
|
|
|
FFileSystem->ReadDirectory(FileList);
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
// Do not retry for initial listing of directory,
|
|
// we instead retry whole connection attempt,
|
|
// what would be done anyway (but Open is not ready for recursion).
|
|
if ((FOpening > 0) ||
|
|
!RobustLoop.TryReopen(E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
while (RobustLoop.Retry());
|
|
|
|
|
|
if (Log->Logging && (Configuration->ActualLogProtocol >= 0))
|
|
{
|
|
for (int Index = 0; Index < FileList->Count; Index++)
|
|
{
|
|
LogRemoteFile(FileList->Files[Index]);
|
|
}
|
|
}
|
|
|
|
ReactOnCommand(fsListDirectory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFileList * __fastcall TTerminal::ReadDirectoryListing(UnicodeString Directory, const TFileMasks & Mask)
|
|
{
|
|
TRemoteFileList * FileList;
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
FileList = NULL;
|
|
TLsSessionAction Action(ActionLog, AbsolutePath(Directory, true));
|
|
|
|
try
|
|
{
|
|
FileList = DoReadDirectoryListing(Directory, false);
|
|
if (FileList != NULL)
|
|
{
|
|
int Index = 0;
|
|
while (Index < FileList->Count)
|
|
{
|
|
TRemoteFile * File = FileList->Files[Index];
|
|
TFileMasks::TParams Params;
|
|
Params.Size = File->Resolve()->Size;
|
|
Params.Modification = File->Modification;
|
|
if (!Mask.MatchesFileName(File->FileName, false, &Params))
|
|
{
|
|
FileList->Delete(Index);
|
|
}
|
|
else
|
|
{
|
|
Index++;
|
|
}
|
|
}
|
|
|
|
Action.FileList(FileList);
|
|
}
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action);
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
return FileList;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFile * __fastcall TTerminal::ReadFileListing(UnicodeString Path)
|
|
{
|
|
TRemoteFile * File;
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
File = NULL;
|
|
TStatSessionAction Action(ActionLog, AbsolutePath(Path, true));
|
|
try
|
|
{
|
|
// reset caches
|
|
AnnounceFileListOperation();
|
|
File = ReadFile(Path);
|
|
Action.File(File);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action);
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
return File;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFileList * __fastcall TTerminal::CustomReadDirectoryListing(UnicodeString Directory, bool UseCache)
|
|
{
|
|
TRemoteFileList * FileList = NULL;
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
FileList = DoReadDirectoryListing(Directory, UseCache);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E);
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
return FileList;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFileList * __fastcall TTerminal::DoReadDirectoryListing(UnicodeString Directory, bool UseCache)
|
|
{
|
|
TRemoteFileList * FileList = new TRemoteFileList();
|
|
try
|
|
{
|
|
bool Cache = UseCache && SessionData->CacheDirectories;
|
|
bool LoadedFromCache = Cache && FDirectoryCache->HasFileList(Directory);
|
|
if (LoadedFromCache)
|
|
{
|
|
LoadedFromCache = FDirectoryCache->GetFileList(Directory, FileList);
|
|
}
|
|
|
|
if (!LoadedFromCache)
|
|
{
|
|
FileList->Directory = Directory;
|
|
|
|
ExceptionOnFail = true;
|
|
try
|
|
{
|
|
ReadDirectory(FileList);
|
|
}
|
|
__finally
|
|
{
|
|
ExceptionOnFail = false;
|
|
}
|
|
|
|
if (Cache)
|
|
{
|
|
AddCachedFileList(FileList);
|
|
}
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
delete FileList;
|
|
throw;
|
|
}
|
|
return FileList;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DeleteContentsIfDirectory(
|
|
const UnicodeString & FileName, const TRemoteFile * File, int Params, TRmSessionAction & Action)
|
|
{
|
|
bool Dir = (File != NULL) && File->IsDirectory && CanRecurseToDirectory(File);
|
|
|
|
if (Dir && FLAGCLEAR(Params, dfNoRecursive))
|
|
{
|
|
try
|
|
{
|
|
ProcessDirectory(FileName, DeleteFile, &Params);
|
|
}
|
|
catch(...)
|
|
{
|
|
Action.Cancel();
|
|
throw;
|
|
}
|
|
}
|
|
return Dir && !File->IsSymLink;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ProcessDirectory(const UnicodeString DirName,
|
|
TProcessFileEvent CallBackFunc, void * Param, bool UseCache, bool IgnoreErrors)
|
|
{
|
|
TRemoteFileList * FileList = NULL;
|
|
if (IgnoreErrors)
|
|
{
|
|
ExceptionOnFail = true;
|
|
try
|
|
{
|
|
try
|
|
{
|
|
FileList = CustomReadDirectoryListing(DirName, UseCache);
|
|
}
|
|
catch(...)
|
|
{
|
|
if (!Active)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
ExceptionOnFail = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FileList = CustomReadDirectoryListing(DirName, UseCache);
|
|
}
|
|
|
|
// skip if directory listing fails and user selects "skip"
|
|
if (FileList)
|
|
{
|
|
try
|
|
{
|
|
UnicodeString Directory = UnixIncludeTrailingBackslash(DirName);
|
|
|
|
TRemoteFile * File;
|
|
for (int Index = 0; Index < FileList->Count; Index++)
|
|
{
|
|
File = FileList->Files[Index];
|
|
if (IsRealFile(File->FileName))
|
|
{
|
|
CallBackFunc(Directory + File->FileName, File, Param);
|
|
// We should catch ESkipFile here as we do in ProcessFiles.
|
|
// Now we have to handle ESkipFile in every callback implementation.
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
delete FileList;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReadDirectory(TRemoteFileList * FileList)
|
|
{
|
|
try
|
|
{
|
|
CustomReadDirectory(FileList);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, FmtLoadStr(LIST_DIR_ERROR, ARRAYOFCONST((FileList->Directory))));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReadSymlink(TRemoteFile * SymlinkFile,
|
|
TRemoteFile *& File)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
try
|
|
{
|
|
LogEvent(FORMAT(L"Reading symlink \"%s\".", (SymlinkFile->FileName)));
|
|
FFileSystem->ReadSymlink(SymlinkFile, File);
|
|
ReactOnCommand(fsReadSymlink);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, FMTLOAD(READ_SYMLINK_ERROR, (SymlinkFile->FileName)));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFile * TTerminal::ReadFile(const UnicodeString & FileName)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
std::unique_ptr<TRemoteFile> File;
|
|
try
|
|
{
|
|
LogEvent(FORMAT(L"Listing file \"%s\".", (FileName)));
|
|
TRemoteFile * AFile = NULL;
|
|
FFileSystem->ReadFile(FileName, AFile);
|
|
File.reset(AFile);
|
|
ReactOnCommand(fsListFile);
|
|
LogRemoteFile(File.get());
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
File.reset(NULL);
|
|
CommandError(&E, FMTLOAD(CANT_GET_ATTRS, (FileName)));
|
|
}
|
|
return File.release();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFile * TTerminal::TryReadFile(const UnicodeString & FileName)
|
|
{
|
|
TRemoteFile * File;
|
|
try
|
|
{
|
|
ExceptionOnFail = true;
|
|
try
|
|
{
|
|
File = ReadFile(UnixExcludeTrailingBackslash(FileName));
|
|
}
|
|
__finally
|
|
{
|
|
ExceptionOnFail = false;
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
if (Active)
|
|
{
|
|
File = NULL;
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
return File;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::FileExists(const UnicodeString & FileName)
|
|
{
|
|
std::unique_ptr<TRemoteFile> File(TryReadFile(FileName));
|
|
return (File.get() != NULL);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::DirectoryExists(const UnicodeString & FileName)
|
|
{
|
|
std::unique_ptr<TRemoteFile> File(TryReadFile(FileName));
|
|
return
|
|
(File.get() != NULL) &&
|
|
File->IsDirectory;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::AnnounceFileListOperation()
|
|
{
|
|
FFileSystem->AnnounceFileListOperation();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OperationFinish(
|
|
TFileOperationProgressType * Progress, const void * /*Item*/, const UnicodeString & FileName,
|
|
bool Success, TOnceDoneOperation & OnceDoneOperation)
|
|
{
|
|
Progress->Finish(FileName, Success, OnceDoneOperation);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OperationStart(
|
|
TFileOperationProgressType & Progress, TFileOperation Operation, TOperationSide Side, int Count)
|
|
{
|
|
OperationStart(Progress, Operation, Side, Count, false, UnicodeString(), 0, odoIdle);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OperationStart(
|
|
TFileOperationProgressType & Progress, TFileOperation Operation, TOperationSide Side, int Count,
|
|
bool Temp, const UnicodeString & Directory, unsigned long CPSLimit, TOnceDoneOperation OnceDoneOperation)
|
|
{
|
|
if (FOperationProgressPersistence != NULL)
|
|
{
|
|
Progress.Restore(*FOperationProgressPersistence);
|
|
}
|
|
Progress.Start(Operation, Side, Count, Temp, Directory, CPSLimit, OnceDoneOperation);
|
|
DebugAssert(FOperationProgress == NULL);
|
|
FOperationProgress = &Progress;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OperationStop(TFileOperationProgressType & Progress)
|
|
{
|
|
DebugAssert(FOperationProgress == &Progress);
|
|
if (FOperationProgressPersistence != NULL)
|
|
{
|
|
Progress.Store(*FOperationProgressPersistence);
|
|
}
|
|
FOperationProgress = NULL;
|
|
Progress.Stop();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::ProcessFiles(TStrings * FileList,
|
|
TFileOperation Operation, TProcessFileEvent ProcessFile, void * Param,
|
|
TOperationSide Side, bool Ex)
|
|
{
|
|
DebugAssert(FileList);
|
|
|
|
bool Result = false;
|
|
TOnceDoneOperation OnceDoneOperation = odoIdle;
|
|
|
|
try
|
|
{
|
|
TFileOperationProgressType Progress(&DoProgress, &DoFinished);
|
|
OperationStart(Progress, Operation, Side, FileList->Count);
|
|
|
|
try
|
|
{
|
|
if (Side == osRemote)
|
|
{
|
|
DebugAssert(FFileSystem != NULL);
|
|
BeginTransaction();
|
|
}
|
|
|
|
try
|
|
{
|
|
int Index = 0;
|
|
UnicodeString FileName;
|
|
bool Success;
|
|
while ((Index < FileList->Count) && (Progress.Cancel == csContinue))
|
|
{
|
|
FileName = FileList->Strings[Index];
|
|
TRemoteFile * File = reinterpret_cast<TRemoteFile *>(FileList->Objects[Index]);
|
|
try
|
|
{
|
|
try
|
|
{
|
|
Success = false;
|
|
if (!Ex)
|
|
{
|
|
ProcessFile(FileName, File, Param);
|
|
}
|
|
else
|
|
{
|
|
// not used anymore
|
|
TProcessFileEventEx ProcessFileEx = reinterpret_cast<TProcessFileEventEx>(ProcessFile);
|
|
ProcessFileEx(FileName, File, Param, Index);
|
|
}
|
|
Success = true;
|
|
}
|
|
__finally
|
|
{
|
|
OperationFinish(&Progress, File, FileName, Success, OnceDoneOperation);
|
|
}
|
|
}
|
|
catch(ESkipFile & E)
|
|
{
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
Index++;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (Side == osRemote)
|
|
{
|
|
EndTransaction();
|
|
}
|
|
}
|
|
|
|
if (Progress.Cancel == csContinue)
|
|
{
|
|
Result = true;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
OperationStop(Progress);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
OnceDoneOperation = odoIdle;
|
|
// this was missing here. was it by purpose?
|
|
// without it any error message is lost
|
|
throw;
|
|
}
|
|
|
|
if (OnceDoneOperation != odoIdle)
|
|
{
|
|
CloseOnCompletion(OnceDoneOperation);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
// not used anymore
|
|
bool __fastcall TTerminal::ProcessFilesEx(TStrings * FileList, TFileOperation Operation,
|
|
TProcessFileEventEx ProcessFile, void * Param, TOperationSide Side)
|
|
{
|
|
return ProcessFiles(FileList, Operation, TProcessFileEvent(ProcessFile),
|
|
Param, Side, true);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TStrings * __fastcall TTerminal::GetFixedPaths()
|
|
{
|
|
DebugAssert(FFileSystem != NULL);
|
|
return FFileSystem->GetFixedPaths();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetResolvingSymlinks()
|
|
{
|
|
return SessionData->ResolveSymlinks && IsCapable[fcResolveSymlink];
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TUsableCopyParamAttrs __fastcall TTerminal::UsableCopyParamAttrs(int Params)
|
|
{
|
|
TUsableCopyParamAttrs Result;
|
|
Result.General =
|
|
FLAGMASK(!IsCapable[fcTextMode], cpaNoTransferMode) |
|
|
FLAGMASK(!IsCapable[fcModeChanging], cpaNoRights) |
|
|
FLAGMASK(!IsCapable[fcModeChanging], cpaNoPreserveReadOnly) |
|
|
FLAGMASK(FLAGSET(Params, cpDelete), cpaNoClearArchive) |
|
|
FLAGMASK(!IsCapable[fcIgnorePermErrors], cpaNoIgnorePermErrors) |
|
|
// the following three are never supported for download,
|
|
// so when they are not supported for upload too,
|
|
// set them in General flags, so that they do not get enabled on
|
|
// Synchronize dialog.
|
|
FLAGMASK(!IsCapable[fcModeChangingUpload], cpaNoRights) |
|
|
FLAGMASK(!IsCapable[fcRemoveCtrlZUpload], cpaNoRemoveCtrlZ) |
|
|
FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM) |
|
|
FLAGMASK(!IsCapable[fcPreservingTimestampDirs], cpaNoPreserveTimeDirs) |
|
|
FLAGMASK(!IsCapable[fcResumeSupport], cpaNoResumeSupport) |
|
|
FLAGMASK(!IsEncryptingFiles(), cpaNoEncryptNewFiles);
|
|
Result.Download = Result.General | cpaNoClearArchive |
|
|
cpaNoIgnorePermErrors |
|
|
// May be already set in General flags, but it's unconditional here
|
|
cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM | cpaNoEncryptNewFiles;
|
|
Result.Upload = Result.General | cpaNoPreserveReadOnly |
|
|
FLAGMASK(!IsCapable[fcPreservingTimestampUpload], cpaNoPreserveTime);
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::IsRecycledFile(UnicodeString FileName)
|
|
{
|
|
bool Result = !SessionData->RecycleBinPath.IsEmpty();
|
|
if (Result)
|
|
{
|
|
UnicodeString Path = UnixExtractFilePath(FileName);
|
|
if (Path.IsEmpty())
|
|
{
|
|
Path = CurrentDirectory;
|
|
}
|
|
Result = UnixSamePath(Path, SessionData->RecycleBinPath);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::RecycleFile(const UnicodeString & AFileName, const TRemoteFile * File)
|
|
{
|
|
UnicodeString FileName = AFileName;
|
|
if (FileName.IsEmpty())
|
|
{
|
|
DebugAssert(File != NULL);
|
|
FileName = File->FileName;
|
|
}
|
|
|
|
bool Result;
|
|
if (IsRecycledFile(FileName))
|
|
{
|
|
Result = true;
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Moving file \"%s\" to remote recycle bin '%s'.",
|
|
(FileName, SessionData->RecycleBinPath)));
|
|
|
|
TMoveFileParams Params;
|
|
Params.Target = SessionData->RecycleBinPath;
|
|
Params.FileMask = FORMAT(L"*-%s.*", (FormatDateTime(L"yyyymmdd-hhnnss", Now())));
|
|
Params.DontOverwrite = false;
|
|
|
|
Result = DoMoveFile(FileName, File, &Params);
|
|
|
|
if (Result && (OperationProgress != NULL) && (OperationProgress->Operation == foDelete))
|
|
{
|
|
OperationProgress->Succeeded();
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::TryStartOperationWithFile(
|
|
const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2)
|
|
{
|
|
bool Result = true;
|
|
if ((OperationProgress != NULL) &&
|
|
((OperationProgress->Operation == Operation1) ||
|
|
((Operation2 != foNone) && (OperationProgress->Operation == Operation2))))
|
|
{
|
|
if (OperationProgress->Cancel != csContinue)
|
|
{
|
|
Result = false;
|
|
}
|
|
else
|
|
{
|
|
OperationProgress->SetFile(FileName);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::StartOperationWithFile(
|
|
const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2)
|
|
{
|
|
if (!TryStartOperationWithFile(FileName, Operation1, Operation2))
|
|
{
|
|
Abort();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DeleteFile(UnicodeString FileName,
|
|
const TRemoteFile * File, void * AParams)
|
|
{
|
|
if (FileName.IsEmpty() && File)
|
|
{
|
|
FileName = File->FileName;
|
|
}
|
|
StartOperationWithFile(FileName, foDelete);
|
|
int Params = (AParams != NULL) ? *static_cast<int*>(AParams) : 0;
|
|
bool Recycle =
|
|
FLAGCLEAR(Params, dfForceDelete) &&
|
|
(SessionData->DeleteToRecycleBin != FLAGSET(Params, dfAlternative)) &&
|
|
!SessionData->RecycleBinPath.IsEmpty();
|
|
if (Recycle && !IsRecycledFile(FileName))
|
|
{
|
|
RecycleFile(FileName, File);
|
|
}
|
|
else
|
|
{
|
|
DoDeleteFile(FFileSystem, FileName, File, Params);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoDeleteFile(
|
|
TCustomFileSystem * FileSystem, const UnicodeString & FileName, const TRemoteFile * File, int Params)
|
|
{
|
|
LogEvent(FORMAT(L"Deleting file \"%s\".", (FileName)));
|
|
FileModified(File, FileName, true);
|
|
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
TRmSessionAction Action(ActionLog, AbsolutePath(FileName, true));
|
|
try
|
|
{
|
|
DebugAssert(FileSystem != NULL);
|
|
// 'File' parameter: SFTPFileSystem needs to know if file is file or directory
|
|
FileSystem->DeleteFile(FileName, File, Params, Action);
|
|
if ((OperationProgress != NULL) && (OperationProgress->Operation == foDelete))
|
|
{
|
|
OperationProgress->Succeeded();
|
|
}
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action, FMTLOAD(DELETE_FILE_ERROR, (FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
|
|
// Forget if file was or was not encrypted and use user preferences, if we ever recreate it.
|
|
FEncryptedFileNames.erase(AbsolutePath(FileName, true));
|
|
ReactOnCommand(fsDeleteFile);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DeleteFiles(TStrings * FilesToDelete, int Params)
|
|
{
|
|
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor, false);
|
|
|
|
// TODO: avoid resolving symlinks while reading subdirectories.
|
|
// Resolving does not work anyway for relative symlinks in subdirectories
|
|
// (at least for SFTP).
|
|
return ProcessFiles(FilesToDelete, foDelete, DeleteFile, &Params);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DeleteLocalFile(UnicodeString FileName,
|
|
const TRemoteFile * /*File*/, void * Params)
|
|
{
|
|
StartOperationWithFile(FileName, foDelete);
|
|
int Deleted;
|
|
if (OnDeleteLocalFile == NULL)
|
|
{
|
|
Deleted = RecursiveDeleteFileChecked(FileName, false);
|
|
}
|
|
else
|
|
{
|
|
OnDeleteLocalFile(FileName, FLAGSET(*static_cast<int*>(Params), dfAlternative), Deleted);
|
|
}
|
|
AppLogFmt(L"Deleted local file \"%s\"", (FileName));
|
|
if (DebugAlwaysTrue((OperationProgress != NULL) && (OperationProgress->Operation == foDelete)))
|
|
{
|
|
OperationProgress->Succeeded(Deleted);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DeleteLocalFiles(TStrings * FileList, int Params)
|
|
{
|
|
return ProcessFiles(FileList, foDelete, DeleteLocalFile, &Params, osLocal);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CustomCommandOnFile(UnicodeString FileName,
|
|
const TRemoteFile * File, void * AParams)
|
|
{
|
|
TCustomCommandParams * Params = static_cast<TCustomCommandParams *>(AParams);
|
|
if (FileName.IsEmpty() && File)
|
|
{
|
|
FileName = File->FileName;
|
|
}
|
|
StartOperationWithFile(FileName, foCustomCommand);
|
|
LogEvent(FORMAT(L"Executing custom command \"%s\" (%d) on file \"%s\".",
|
|
(Params->Command, Params->Params, FileName)));
|
|
FileModified(File, FileName);
|
|
DoCustomCommandOnFile(FileName, File, Params->Command, Params->Params,
|
|
Params->OutputEvent);
|
|
ReactOnCommand(fsAnyCommand);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::PrepareCommandSession(bool NeedCurrentDirectory)
|
|
{
|
|
DebugAssert(CommandSessionOpened);
|
|
DebugAssert(FCommandSession->FSProtocol == cfsSCP);
|
|
LogEvent(L"Performing operation on command session.");
|
|
|
|
if (FCommandSession->CurrentDirectory != CurrentDirectory)
|
|
{
|
|
FCommandSession->CurrentDirectory = CurrentDirectory;
|
|
// We are likely in transaction, so ReadCurrentDirectory won't get called
|
|
// until transaction ends. But we need to know CurrentDirectory to
|
|
// expand !/ pattern when in CustomCommandOnFiles.
|
|
// Doing this only, when current directory of the main and secondary shell differs,
|
|
// what would be the case before the first file in transaction.
|
|
// Otherwise we would be reading pwd before every time as the
|
|
// CustomCommandOnFile on its own sets FReadCurrentDirectoryPending
|
|
if (NeedCurrentDirectory && FCommandSession->FReadCurrentDirectoryPending)
|
|
{
|
|
FCommandSession->ReadCurrentDirectory();
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TCustomFileSystem * TTerminal::GetFileSystemForCapability(TFSCapability Capability, bool NeedCurrentDirectory)
|
|
{
|
|
TCustomFileSystem * Result;
|
|
if (IsCapable[Capability])
|
|
{
|
|
DebugAssert(FFileSystem != NULL);
|
|
Result = FFileSystem;
|
|
}
|
|
else
|
|
{
|
|
PrepareCommandSession(NeedCurrentDirectory);
|
|
|
|
Result = FCommandSession->FFileSystem;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCustomCommandOnFile(UnicodeString FileName,
|
|
const TRemoteFile * File, UnicodeString Command, int Params,
|
|
TCaptureOutputEvent OutputEvent)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
TCustomFileSystem * FileSystem = GetFileSystemForCapability(fcAnyCommand, true);
|
|
DebugAssert((FileSystem != FFileSystem) || IsCapable[fcShellAnyCommand]);
|
|
|
|
FileSystem->CustomCommandOnFile(FileName, File, Command, Params, OutputEvent);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, FMTLOAD(CUSTOM_COMMAND_ERROR, (Command, FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CustomCommandOnFiles(UnicodeString Command,
|
|
int Params, TStrings * Files, TCaptureOutputEvent OutputEvent)
|
|
{
|
|
if (!TRemoteCustomCommand().IsFileListCommand(Command))
|
|
{
|
|
TCustomCommandParams AParams;
|
|
AParams.Command = Command;
|
|
AParams.Params = Params;
|
|
AParams.OutputEvent = OutputEvent;
|
|
ProcessFiles(Files, foCustomCommand, CustomCommandOnFile, &AParams);
|
|
}
|
|
else
|
|
{
|
|
UnicodeString FileList;
|
|
for (int i = 0; i < Files->Count; i++)
|
|
{
|
|
TRemoteFile * File = static_cast<TRemoteFile *>(Files->Objects[i]);
|
|
bool Dir = File->IsDirectory && CanRecurseToDirectory(File);
|
|
|
|
if (!Dir || FLAGSET(Params, ccApplyToDirectories))
|
|
{
|
|
AddToShellFileListCommandLine(FileList, Files->Strings[i]);
|
|
}
|
|
}
|
|
|
|
TCustomCommandData Data(this);
|
|
UnicodeString Cmd =
|
|
TRemoteCustomCommand(Data, CurrentDirectory, L"", FileList).
|
|
Complete(Command, true);
|
|
if (!DoOnCustomCommand(Cmd))
|
|
{
|
|
DoAnyCommand(Cmd, OutputEvent, NULL);
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoOnCustomCommand(const UnicodeString & Command)
|
|
{
|
|
bool Result = false;
|
|
if (FOnCustomCommand != NULL)
|
|
{
|
|
FOnCustomCommand(this, Command, Result);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ChangeFileProperties(UnicodeString FileName,
|
|
const TRemoteFile * File, /*const TRemoteProperties*/ void * Properties)
|
|
{
|
|
TRemoteProperties * RProperties = static_cast<TRemoteProperties *>(Properties);
|
|
DebugAssert(RProperties && !RProperties->Valid.Empty());
|
|
|
|
if (FileName.IsEmpty() && File)
|
|
{
|
|
FileName = File->FileName;
|
|
}
|
|
StartOperationWithFile(FileName, foSetProperties);
|
|
if (Log->Logging)
|
|
{
|
|
LogEvent(FORMAT(L"Changing properties of \"%s\" (%s)",
|
|
(FileName, BooleanToEngStr(RProperties->Recursive))));
|
|
if (RProperties->Valid.Contains(vpRights))
|
|
{
|
|
LogEvent(FORMAT(L" - mode: \"%s\"", (RProperties->Rights.ModeStr)));
|
|
}
|
|
if (RProperties->Valid.Contains(vpGroup))
|
|
{
|
|
LogEvent(FORMAT(L" - group: %s", (RProperties->Group.LogText)));
|
|
}
|
|
if (RProperties->Valid.Contains(vpOwner))
|
|
{
|
|
LogEvent(FORMAT(L" - owner: %s", (RProperties->Owner.LogText)));
|
|
}
|
|
if (RProperties->Valid.Contains(vpModification))
|
|
{
|
|
LogEvent(FORMAT(L" - modification: \"%s\"",
|
|
(FormatDateTime(L"dddddd tt",
|
|
UnixToDateTime(RProperties->Modification, SessionData->DSTMode)))));
|
|
}
|
|
if (RProperties->Valid.Contains(vpLastAccess))
|
|
{
|
|
LogEvent(FORMAT(L" - last access: \"%s\"",
|
|
(FormatDateTime(L"dddddd tt",
|
|
UnixToDateTime(RProperties->LastAccess, SessionData->DSTMode)))));
|
|
}
|
|
}
|
|
FileModified(File, FileName);
|
|
DoChangeFileProperties(FileName, File, RProperties);
|
|
ReactOnCommand(fsChangeProperties);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoChangeFileProperties(const UnicodeString FileName,
|
|
const TRemoteFile * File, const TRemoteProperties * Properties)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
TChmodSessionAction Action(ActionLog, AbsolutePath(FileName, true));
|
|
try
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
FFileSystem->ChangeFileProperties(FileName, File, Properties, Action);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action, FMTLOAD(CHANGE_PROPERTIES_ERROR, (FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ChangeFilesProperties(TStrings * FileList,
|
|
const TRemoteProperties * Properties)
|
|
{
|
|
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor, false);
|
|
|
|
AnnounceFileListOperation();
|
|
ProcessFiles(FileList, foSetProperties, ChangeFileProperties, const_cast<TRemoteProperties *>(Properties));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::LoadFilesProperties(TStrings * FileList)
|
|
{
|
|
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor, false);
|
|
|
|
// see comment in TSFTPFileSystem::IsCapable
|
|
bool Result =
|
|
IsCapable[fcLoadingAdditionalProperties] &&
|
|
FFileSystem->LoadFilesProperties(FileList);
|
|
if (Result && SessionData->CacheDirectories &&
|
|
(FileList->Count > 0) &&
|
|
(dynamic_cast<TRemoteFile *>(FileList->Objects[0])->Directory == FFiles))
|
|
{
|
|
AddCachedFileList(FFiles);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCalculateFileSize(UnicodeString FileName,
|
|
const TRemoteFile * File, void * Param)
|
|
{
|
|
// This is called for top-level entries only
|
|
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Param);
|
|
|
|
if (AParams->Stats->FoundFiles != NULL)
|
|
{
|
|
UnicodeString DirPath = UnixExtractFilePath(UnixExcludeTrailingBackslash(File->FullFileName));
|
|
if ((DirPath != AParams->LastDirPath) ||
|
|
(AParams->Files == NULL))
|
|
{
|
|
AParams->Files = new TCollectedFileList();
|
|
AParams->LastDirPath = DirPath;
|
|
AParams->Stats->FoundFiles->AddObject(AParams->LastDirPath, AParams->Files);
|
|
}
|
|
}
|
|
|
|
__int64 PrevSize = AParams->Size;
|
|
CalculateFileSize(FileName, File, Param);
|
|
|
|
if (AParams->Stats->CalculatedSizes != NULL)
|
|
{
|
|
__int64 Size = AParams->Size - PrevSize;
|
|
AParams->Stats->CalculatedSizes->push_back(Size);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CalculateFileSize(UnicodeString FileName,
|
|
const TRemoteFile * File, /*TCalculateSizeParams*/ void * Param)
|
|
{
|
|
DebugAssert(Param);
|
|
DebugAssert(File);
|
|
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Param);
|
|
|
|
if (FileName.IsEmpty())
|
|
{
|
|
FileName = File->FileName;
|
|
}
|
|
|
|
if (!TryStartOperationWithFile(FileName, foCalculateSize))
|
|
{
|
|
AParams->Result = false;
|
|
// Combined with csIgnoreErrors, this will not abort completely, but we get called again
|
|
// with next sibling of our parent directory (and we get again to this branch, throwing again)
|
|
Abort();
|
|
}
|
|
|
|
if ((AParams->CopyParam == NULL) ||
|
|
DoAllowRemoteFileTransfer(File, AParams->CopyParam, FLAGSET(AParams->Params, csDisallowTemporaryTransferFiles)))
|
|
{
|
|
int CollectionIndex = -1;
|
|
if (AParams->Files != NULL)
|
|
{
|
|
UnicodeString FullFileName = UnixExcludeTrailingBackslash(File->FullFileName);
|
|
CollectionIndex = AParams->Files->Add(FullFileName, File->Duplicate(), File->IsDirectory);
|
|
}
|
|
|
|
if (File->IsDirectory)
|
|
{
|
|
if (CanRecurseToDirectory(File))
|
|
{
|
|
if (!AParams->AllowDirs)
|
|
{
|
|
AParams->Result = false;
|
|
}
|
|
else if (FLAGSET(AParams->Params, csStopOnFirstFile) && (AParams->Stats->Files > 0))
|
|
{
|
|
// do not waste time recursing into a folder, if we already found some files
|
|
}
|
|
else
|
|
{
|
|
if (FLAGCLEAR(AParams->Params, csStopOnFirstFile))
|
|
{
|
|
LogEvent(FORMAT(L"Getting size of directory \"%s\"", (FileName)));
|
|
}
|
|
|
|
// pass in full path so we get it back in file list for AllowTransfer() exclusion
|
|
if (!DoCalculateDirectorySize(File->FullFileName, AParams))
|
|
{
|
|
if (CollectionIndex >= 0)
|
|
{
|
|
AParams->Files->DidNotRecurse(CollectionIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AParams->Stats->Directories++;
|
|
}
|
|
else
|
|
{
|
|
AParams->Size += File->Resolve()->Size;
|
|
|
|
AParams->Stats->Files++;
|
|
}
|
|
|
|
if (File->IsSymLink)
|
|
{
|
|
AParams->Stats->SymLinks++;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoCalculateDirectorySize(const UnicodeString & FileName, TCalculateSizeParams * Params)
|
|
{
|
|
bool Result = false;
|
|
if (FLAGSET(Params->Params, csStopOnFirstFile) && (Configuration->ActualLogProtocol >= 1))
|
|
{
|
|
LogEvent(FORMAT(L"Checking if remote directory \"%s\" is empty", (FileName)));
|
|
}
|
|
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
ProcessDirectory(FileName, CalculateFileSize, Params, Params->UseCache);
|
|
Result = true;
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
// We can probably replace the csIgnoreErrors with IgnoreErrors argument of the ProcessDirectory
|
|
if (!Active || ((Params->Params & csIgnoreErrors) == 0))
|
|
{
|
|
RetryLoop.Error(E, FMTLOAD(CALCULATE_SIZE_ERROR, (FileName)));
|
|
}
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
if (Params->Stats->Files == 0)
|
|
{
|
|
LogEvent(FORMAT(L"Remote directory \"%s\" is empty", (FileName)));
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Remote directory \"%s\" is not empty", (FileName)));
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::CalculateFilesSize(TStrings * FileList, __int64 & Size, TCalculateSizeParams & Params)
|
|
{
|
|
// With FTP protocol, we may use DSIZ command from
|
|
// draft-peterson-streamlined-ftp-command-extensions-10
|
|
// Implemented by Serv-U FTP.
|
|
|
|
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor, false);
|
|
|
|
ProcessFiles(FileList, foCalculateSize, DoCalculateFileSize, &Params);
|
|
Size = Params.Size;
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
LogEvent(FORMAT(L"Size of %d remote files/folders calculated as %s", (FileList->Count, IntToStr(Size))));
|
|
}
|
|
return Params.Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CalculateSubFoldersChecksum(
|
|
const UnicodeString & Alg, TStrings * FileList, TCalculatedChecksumEvent OnCalculatedChecksum,
|
|
TFileOperationProgressType * OperationProgress, bool FirstLevel)
|
|
{
|
|
// recurse into subdirectories only if we have callback function
|
|
if (OnCalculatedChecksum != NULL)
|
|
{
|
|
int Index = 0;
|
|
TOnceDoneOperation OnceDoneOperation; // unused
|
|
while ((Index < FileList->Count) && !OperationProgress->Cancel)
|
|
{
|
|
UnicodeString FileName = FileList->Strings[Index];
|
|
TRemoteFile * File = DebugNotNull(dynamic_cast<TRemoteFile *>(FileList->Objects[Index]));
|
|
|
|
if (File->IsDirectory &&
|
|
CanRecurseToDirectory(File) &&
|
|
IsRealFile(File->FileName))
|
|
{
|
|
OperationProgress->SetFile(File->FileName);
|
|
std::unique_ptr<TRemoteFileList> SubFiles(CustomReadDirectoryListing(File->FullFileName, false));
|
|
|
|
if (SubFiles.get() != NULL)
|
|
{
|
|
std::unique_ptr<TStrings> SubFileList(new TStringList());
|
|
bool Success = false;
|
|
try
|
|
{
|
|
OperationProgress->SetFile(File->FileName);
|
|
|
|
for (int Index = 0; Index < SubFiles->Count; Index++)
|
|
{
|
|
TRemoteFile * SubFile = SubFiles->Files[Index];
|
|
UnicodeString SubFileName = UnixCombinePaths(FileName, SubFile->FileName);
|
|
SubFileList->AddObject(SubFileName, SubFile);
|
|
}
|
|
|
|
FFileSystem->CalculateFilesChecksum(Alg, SubFileList.get(), OnCalculatedChecksum, OperationProgress, false);
|
|
|
|
Success = true;
|
|
}
|
|
__finally
|
|
{
|
|
if (FirstLevel)
|
|
{
|
|
OperationProgress->Finish(File->FileName, Success, OnceDoneOperation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Index++;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CalculateFilesChecksum(
|
|
const UnicodeString & Alg, TStrings * FileList, TCalculatedChecksumEvent OnCalculatedChecksum)
|
|
{
|
|
TFileOperationProgressType Progress(&DoProgress, &DoFinished);
|
|
OperationStart(Progress, foCalculateChecksum, osRemote, FileList->Count);
|
|
|
|
try
|
|
{
|
|
TCustomFileSystem * FileSystem = GetFileSystemForCapability(fcCalculatingChecksum);
|
|
|
|
UnicodeString NormalizedAlg = FileSystem->CalculateFilesChecksumInitialize(Alg);
|
|
|
|
FileSystem->CalculateFilesChecksum(NormalizedAlg, FileList, OnCalculatedChecksum, &Progress, true);
|
|
}
|
|
__finally
|
|
{
|
|
OperationStop(Progress);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::RenameFile(const TRemoteFile * File, const UnicodeString & NewName)
|
|
{
|
|
// Already checked in TUnixDirView::InternalEdit
|
|
if (DebugAlwaysTrue(File->FileName != NewName))
|
|
{
|
|
FileModified(File, File->FileName);
|
|
LogEvent(FORMAT(L"Renaming file \"%s\" to \"%s\".", (File->FileName, NewName)));
|
|
if (DoRenameFile(File->FileName, File, NewName, false, false))
|
|
{
|
|
ReactOnCommand(fsRenameFile);
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::DoRenameOrCopyFile(
|
|
bool Rename, const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & NewName,
|
|
bool Move, bool DontOverwrite, bool IsBatchOperation)
|
|
{
|
|
TBatchOverwrite BatchOverwrite = (IsBatchOperation ? OperationProgress->BatchOverwrite : boNo);
|
|
UnicodeString AbsoluteFileName = AbsolutePath(FileName, true);
|
|
UnicodeString AbsoluteNewName = AbsolutePath(NewName, true);
|
|
bool Result = true;
|
|
bool ExistenceKnown = false;
|
|
std::unique_ptr<TRemoteFile> DuplicateFile;
|
|
if (UnixSamePath(AbsoluteFileName, AbsoluteNewName))
|
|
{
|
|
LogEvent(FORMAT(L"Target \"%s\" is same as source \"%s\" - skipping.", (AbsoluteNewName, AbsoluteFileName)));
|
|
Result = false;
|
|
}
|
|
else if (BatchOverwrite == boNone)
|
|
{
|
|
DebugAssert(!DontOverwrite); // unsupported combination
|
|
Result = !FileExists(AbsoluteNewName);
|
|
ExistenceKnown = true;
|
|
}
|
|
else if (BatchOverwrite == boAll)
|
|
{
|
|
// noop
|
|
}
|
|
else if (DebugAlwaysTrue(BatchOverwrite == boNo) &&
|
|
Configuration->ConfirmOverwriting &&
|
|
!DontOverwrite)
|
|
{
|
|
DuplicateFile.reset(TryReadFile(AbsoluteNewName));
|
|
ExistenceKnown = true;
|
|
|
|
if (DuplicateFile.get() != NULL)
|
|
{
|
|
UnicodeString QuestionFmt;
|
|
TQueryType QueryType;
|
|
if (DuplicateFile->IsDirectory)
|
|
{
|
|
QuestionFmt = MainInstructions(LoadStr(DIRECTORY_OVERWRITE)) + LoadStr(DIRECTORY_OVERWRITE_WARNING);
|
|
QueryType = qtWarning;
|
|
}
|
|
else
|
|
{
|
|
QuestionFmt = MainInstructions(LoadStr(FILE_OVERWRITE));
|
|
QueryType = qtConfirmation;
|
|
}
|
|
TQueryParams Params(qpNeverAskAgainCheck);
|
|
UnicodeString Question = FORMAT(QuestionFmt, (NewName));
|
|
unsigned int Answers = qaYes | qaNo | FLAGMASK(OperationProgress != NULL, qaCancel) | FLAGMASK(IsBatchOperation, qaYesToAll | qaNoToAll);
|
|
unsigned int Answer = QueryUser(Question, NULL, Answers, &Params, QueryType);
|
|
switch (Answer)
|
|
{
|
|
case qaNeverAskAgain:
|
|
Configuration->ConfirmOverwriting = false;
|
|
Result = true;
|
|
break;
|
|
|
|
case qaYes:
|
|
Result = true;
|
|
break;
|
|
|
|
case qaNo:
|
|
Result = false;
|
|
break;
|
|
|
|
case qaYesToAll:
|
|
Result = true;
|
|
if (DebugAlwaysTrue(IsBatchOperation))
|
|
{
|
|
OperationProgress->SetBatchOverwrite(boAll);
|
|
}
|
|
break;
|
|
|
|
case qaNoToAll:
|
|
Result = false;
|
|
if (DebugAlwaysTrue(IsBatchOperation))
|
|
{
|
|
OperationProgress->SetBatchOverwrite(boNone);
|
|
}
|
|
break;
|
|
|
|
case qaCancel:
|
|
Result = false;
|
|
OperationProgress->SetCancel(csCancel);
|
|
break;
|
|
|
|
default:
|
|
Result = false;
|
|
DebugFail();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
// deliberatelly after confirmation, so that the user can see what files are being overwritten
|
|
if (StartsStr(UnixIncludeTrailingBackslash(AbsoluteNewName), AbsoluteFileName))
|
|
{
|
|
throw ExtException(MainInstructions(LoadStr(ANCESTOR_TARGET_ERROR)), FORMAT(L"%s => %s", (AbsoluteFileName, AbsoluteNewName)));
|
|
}
|
|
|
|
// Prevent destroying TRemoteFile between delete and rename
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
TCustomFileSystem * FileSystem;
|
|
if (!Rename)
|
|
{
|
|
if (IsCapable[fcSecondaryShell] &&
|
|
File->IsDirectory &&
|
|
(FCommandSession != NULL)) // Won't be in scripting, there we let it fail later
|
|
{
|
|
PrepareCommandSession();
|
|
FileSystem = FCommandSession->FFileSystem;
|
|
}
|
|
else
|
|
{
|
|
FileSystem = GetFileSystemForCapability(fcRemoteCopy);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FileSystem = FFileSystem;
|
|
}
|
|
|
|
if (!IsCapable[fcMoveOverExistingFile] && !DontOverwrite)
|
|
{
|
|
if (!ExistenceKnown)
|
|
{
|
|
DuplicateFile.reset(TryReadFile(AbsoluteNewName));
|
|
}
|
|
|
|
if (DuplicateFile.get() != NULL)
|
|
{
|
|
DoDeleteFile(FileSystem, AbsoluteNewName, DuplicateFile.get(), 0);
|
|
}
|
|
}
|
|
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
DebugAssert(FileSystem != NULL);
|
|
if (Rename)
|
|
{
|
|
TMvSessionAction Action(ActionLog, AbsoluteFileName, AbsoluteNewName);
|
|
try
|
|
{
|
|
FileSystem->RenameFile(FileName, File, NewName, !DontOverwrite);
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
UnicodeString Message = FMTLOAD(Move ? MOVE_FILE_ERROR : RENAME_FILE_ERROR, (FileName, NewName));
|
|
RetryLoop.Error(E, Action, Message);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TCpSessionAction Action(ActionLog, AbsoluteFileName, AbsoluteNewName);
|
|
try
|
|
{
|
|
FileSystem->CopyFile(FileName, File, NewName, !DontOverwrite);
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action, FMTLOAD(COPY_FILE_ERROR, (FileName, NewName)));
|
|
}
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
Result = RetryLoop.Succeeded();
|
|
}
|
|
__finally
|
|
{
|
|
EndTransaction();
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoRenameFile(
|
|
const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & NewName, bool Move, bool DontOverwrite)
|
|
{
|
|
// Can be foDelete when recycling (and overwrite should not happen in this case)
|
|
bool IsBatchOperation = (OperationProgress != NULL) && (OperationProgress->Operation == foRemoteMove);
|
|
return DoRenameOrCopyFile(true, FileName, File, NewName, Move, DontOverwrite, IsBatchOperation);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoMoveFile(const UnicodeString & FileName, const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
|
|
{
|
|
StartOperationWithFile(FileName, foRemoteMove, foDelete);
|
|
DebugAssert(Param != NULL);
|
|
const TMoveFileParams & Params = *static_cast<const TMoveFileParams*>(Param);
|
|
UnicodeString NewName = UnixIncludeTrailingBackslash(Params.Target) +
|
|
MaskFileName(UnixExtractFileName(FileName), Params.FileMask);
|
|
LogEvent(FORMAT(L"Moving file \"%s\" to \"%s\".", (FileName, NewName)));
|
|
FileModified(File, FileName);
|
|
bool Result = DoRenameFile(FileName, File, NewName, true, Params.DontOverwrite);
|
|
if (Result)
|
|
{
|
|
ReactOnCommand(fsMoveFile);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::MoveFile(const UnicodeString FileName, const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
|
|
{
|
|
DoMoveFile(FileName, File, Param);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::MoveFiles(
|
|
TStrings * FileList, const UnicodeString & Target, const UnicodeString & FileMask, bool DontOverwrite)
|
|
{
|
|
TMoveFileParams Params;
|
|
Params.Target = Target;
|
|
Params.FileMask = FileMask;
|
|
Params.DontOverwrite = DontOverwrite;
|
|
bool Result;
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
Result = ProcessFiles(FileList, foRemoteMove, MoveFile, &Params);
|
|
}
|
|
__finally
|
|
{
|
|
if (Active)
|
|
{
|
|
// Only after the move, as with encryption, the folders can be read and cached before the move
|
|
// (when determining if the target exists and is encrypted)
|
|
DirectoryModified(Target, true);
|
|
|
|
UnicodeString WithTrailing = UnixIncludeTrailingBackslash(CurrentDirectory);
|
|
bool PossiblyMoved = false;
|
|
// check if we was moving current directory.
|
|
// this is just optimization to avoid checking existence of current
|
|
// directory after each move operation.
|
|
for (int Index = 0; !PossiblyMoved && (Index < FileList->Count); Index++)
|
|
{
|
|
const TRemoteFile * File =
|
|
dynamic_cast<const TRemoteFile *>(FileList->Objects[Index]);
|
|
// File can be NULL, and filename may not be full path,
|
|
// but currently this is the only way we can move (at least in GUI)
|
|
// current directory
|
|
if ((File != NULL) &&
|
|
File->IsDirectory &&
|
|
((CurrentDirectory.SubString(1, FileList->Strings[Index].Length()) == FileList->Strings[Index]) &&
|
|
((FileList->Strings[Index].Length() == CurrentDirectory.Length()) ||
|
|
(CurrentDirectory[FileList->Strings[Index].Length() + 1] == L'/'))))
|
|
{
|
|
PossiblyMoved = true;
|
|
}
|
|
}
|
|
|
|
if (PossiblyMoved && !FileExists(CurrentDirectory))
|
|
{
|
|
UnicodeString NearestExisting = CurrentDirectory;
|
|
do
|
|
{
|
|
NearestExisting = UnixExtractFileDir(NearestExisting);
|
|
}
|
|
while (!IsUnixRootPath(NearestExisting) && !FileExists(NearestExisting));
|
|
|
|
ChangeDirectory(NearestExisting);
|
|
}
|
|
}
|
|
EndTransaction();
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCopyFile(
|
|
const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & NewName, bool DontOverwrite)
|
|
{
|
|
bool IsBatchOperation = DebugAlwaysTrue(OperationProgress != NULL);
|
|
bool Move = false; // not used
|
|
DoRenameOrCopyFile(false, FileName, File, NewName, Move, DontOverwrite, IsBatchOperation);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CopyFile(const UnicodeString FileName,
|
|
const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
|
|
{
|
|
StartOperationWithFile(FileName, foRemoteCopy);
|
|
DebugAssert(Param != NULL);
|
|
const TMoveFileParams & Params = *static_cast<const TMoveFileParams*>(Param);
|
|
UnicodeString NewName = UnixIncludeTrailingBackslash(Params.Target) +
|
|
MaskFileName(UnixExtractFileName(FileName), Params.FileMask);
|
|
LogEvent(FORMAT(L"Copying file \"%s\" to \"%s\".", (FileName, NewName)));
|
|
DoCopyFile(FileName, File, NewName, Params.DontOverwrite);
|
|
ReactOnCommand(fsCopyFile);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CopyFiles(
|
|
TStrings * FileList, const UnicodeString & Target, const UnicodeString & FileMask, bool DontOverwrite)
|
|
{
|
|
TMoveFileParams Params;
|
|
Params.Target = Target;
|
|
Params.FileMask = FileMask;
|
|
Params.DontOverwrite = DontOverwrite;
|
|
DirectoryModified(Target, true);
|
|
return ProcessFiles(FileList, foRemoteCopy, CopyFile, &Params);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
DebugAssert(Properties != NULL);
|
|
EnsureNonExistence(DirName);
|
|
FileModified(NULL, DirName);
|
|
|
|
LogEvent(FORMAT(L"Creating directory \"%s\".", (DirName)));
|
|
bool Encrypt = Properties->Valid.Contains(vpEncrypt) && Properties->Encrypt;
|
|
DoCreateDirectory(DirName, Encrypt);
|
|
|
|
TValidProperties RemainingPropeties = Properties->Valid - (TValidProperties() << vpEncrypt);
|
|
if (!RemainingPropeties.Empty() &&
|
|
(IsCapable[fcModeChanging] || IsCapable[fcOwnerChanging] || IsCapable[fcGroupChanging]))
|
|
{
|
|
DoChangeFileProperties(DirName, NULL, Properties);
|
|
}
|
|
|
|
ReactOnCommand(fsCreateDirectory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCreateDirectory(const UnicodeString & DirName, bool Encrypt)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
TMkdirSessionAction Action(ActionLog, AbsolutePath(DirName, true));
|
|
try
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
FFileSystem->CreateDirectory(DirName, Encrypt);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, Action, FMTLOAD(CREATE_DIR_ERROR, (DirName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CreateLink(const UnicodeString FileName,
|
|
const UnicodeString PointTo, bool Symbolic)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
EnsureNonExistence(FileName);
|
|
if (SessionData->CacheDirectories)
|
|
{
|
|
DirectoryModified(CurrentDirectory, false);
|
|
}
|
|
|
|
LogEvent(FORMAT(L"Creating link \"%s\" to \"%s\" (symbolic: %s).",
|
|
(FileName, PointTo, BooleanToEngStr(Symbolic))));
|
|
DoCreateLink(FileName, PointTo, Symbolic);
|
|
ReactOnCommand(fsCreateDirectory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCreateLink(const UnicodeString FileName,
|
|
const UnicodeString PointTo, bool Symbolic)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
FFileSystem->CreateLink(FileName, PointTo, Symbolic);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, FMTLOAD(CREATE_LINK_ERROR, (FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::HomeDirectory()
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
try
|
|
{
|
|
LogEvent(L"Changing directory to home directory.");
|
|
FFileSystem->HomeDirectory();
|
|
ReactOnCommand(fsHomeDirectory);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, LoadStr(CHANGE_HOMEDIR_ERROR));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetHomeDirectory()
|
|
{
|
|
return FFileSystem->GetHomeDirectory();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ChangeDirectory(const UnicodeString Directory)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
try
|
|
{
|
|
UnicodeString CachedDirectory;
|
|
DebugAssert(!SessionData->CacheDirectoryChanges || (FDirectoryChangesCache != NULL));
|
|
// never use directory change cache during startup, this ensures, we never
|
|
// end-up initially in non-existing directory
|
|
if ((Status == ssOpened) &&
|
|
SessionData->CacheDirectoryChanges &&
|
|
FDirectoryChangesCache->GetDirectoryChange(PeekCurrentDirectory(),
|
|
Directory, CachedDirectory))
|
|
{
|
|
LogEvent(FORMAT(L"Cached directory change via \"%s\" to \"%s\".",
|
|
(Directory, CachedDirectory)));
|
|
FFileSystem->CachedChangeDirectory(CachedDirectory);
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Changing directory to \"%s\".", (Directory)));
|
|
FFileSystem->ChangeDirectory(Directory);
|
|
}
|
|
FLastDirectoryChange = Directory;
|
|
ReactOnCommand(fsChangeDirectory);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, FMTLOAD(CHANGE_DIR_ERROR, (Directory)));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LookupUsersGroups()
|
|
{
|
|
if (!FUsersGroupsLookedup && (SessionData->LookupUserGroups != asOff) &&
|
|
IsCapable[fcUserGroupListing])
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
|
|
try
|
|
{
|
|
FUsersGroupsLookedup = true;
|
|
LogEvent(L"Looking up groups and users.");
|
|
FFileSystem->LookupUsersGroups();
|
|
ReactOnCommand(fsLookupUsersGroups);
|
|
|
|
if (Log->Logging)
|
|
{
|
|
FGroups.Log(this, L"groups");
|
|
FMembership.Log(this, L"membership");
|
|
FUsers.Log(this, L"users");
|
|
}
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
if (!Active || (SessionData->LookupUserGroups == asOn))
|
|
{
|
|
CommandError(&E, LoadStr(LOOKUP_GROUPS_ERROR));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::AllowedAnyCommand(const UnicodeString Command)
|
|
{
|
|
return !Command.Trim().IsEmpty();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetCommandSessionOpened()
|
|
{
|
|
// consider secondary terminal open in "ready" state only
|
|
// so we never do keepalives on it until it is completely initialized
|
|
return (FCommandSession != NULL) &&
|
|
(FCommandSession->Status == ssOpened);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminal::CreateSecondarySession(const UnicodeString & Name, TSessionData * SessionData)
|
|
{
|
|
std::unique_ptr<TTerminal> Result(new TSecondaryTerminal(this, SessionData, Configuration, Name, ActionLog));
|
|
|
|
Result->AutoReadDirectory = false;
|
|
|
|
Result->FExceptionOnFail = FExceptionOnFail;
|
|
|
|
Result->OnQueryUser = OnQueryUser;
|
|
Result->OnPromptUser = OnPromptUser;
|
|
Result->OnShowExtendedException = OnShowExtendedException;
|
|
Result->OnProgress = OnProgress;
|
|
Result->OnFinished = OnFinished;
|
|
Result->OnInformation = OnInformation;
|
|
Result->OnCustomCommand = OnCustomCommand;
|
|
// do not copy OnDisplayBanner to avoid it being displayed
|
|
return Result.release();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FillSessionDataForCode(TSessionData * Data)
|
|
{
|
|
const TSessionInfo & SessionInfo = GetSessionInfo();
|
|
if (!SessionInfo.HostKeyFingerprintSHA256.IsEmpty())
|
|
{
|
|
Data->HostKey = SessionInfo.HostKeyFingerprintSHA256;
|
|
}
|
|
else if (SessionInfo.CertificateVerifiedManually && DebugAlwaysTrue(!SessionInfo.CertificateFingerprintSHA256.IsEmpty()))
|
|
{
|
|
Data->HostKey = SessionInfo.CertificateFingerprintSHA256;
|
|
}
|
|
|
|
if (FTunnel != NULL)
|
|
{
|
|
const TSessionInfo & TunnelSessionInfo = FTunnel->GetSessionInfo();
|
|
if (DebugAlwaysTrue(!TunnelSessionInfo.HostKeyFingerprintSHA256.IsEmpty()))
|
|
{
|
|
Data->TunnelHostKey = TunnelSessionInfo.HostKeyFingerprintSHA256;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::UpdateSessionCredentials(TSessionData * Data)
|
|
{
|
|
// Only if it differs from the original session data?
|
|
// Because this way, we persist the username redundantly in workspace session linked to a stored site.
|
|
Data->UserName = UserName;
|
|
Data->Password = Password;
|
|
if (!FRememberedPassword.IsEmpty() && (FRememberedPasswordKind == pkPassphrase))
|
|
{
|
|
Data->Passphrase = GetRememberedPassword();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminal::GetCommandSession()
|
|
{
|
|
if ((FCommandSession != NULL) && !FCommandSession->Active)
|
|
{
|
|
SAFE_DESTROY(FCommandSession);
|
|
}
|
|
|
|
if (FCommandSession == NULL)
|
|
{
|
|
// transaction cannot be started yet to allow proper matching transaction
|
|
// levels between main and command session
|
|
DebugAssert(FInTransaction == 0);
|
|
|
|
std::unique_ptr<TSessionData> CommandSessionData(SessionData->Clone());
|
|
CommandSessionData->RemoteDirectory = CurrentDirectory;
|
|
CommandSessionData->FSProtocol = fsSCPonly;
|
|
CommandSessionData->ClearAliases = false;
|
|
CommandSessionData->UnsetNationalVars = false;
|
|
CommandSessionData->LookupUserGroups = asOff;
|
|
|
|
FCommandSession = CreateSecondarySession(L"Shell", CommandSessionData.get());
|
|
|
|
FCommandSession->AutoReadDirectory = false;
|
|
|
|
FCommandSession->FExceptionOnFail = FExceptionOnFail;
|
|
|
|
FCommandSession->OnQueryUser = OnQueryUser;
|
|
FCommandSession->OnPromptUser = OnPromptUser;
|
|
FCommandSession->OnShowExtendedException = OnShowExtendedException;
|
|
FCommandSession->OnProgress = OnProgress;
|
|
FCommandSession->OnFinished = OnFinished;
|
|
FCommandSession->OnInformation = OnInformation;
|
|
FCommandSession->OnCustomCommand = OnCustomCommand;
|
|
// do not copy OnDisplayBanner to avoid it being displayed
|
|
}
|
|
|
|
return FCommandSession;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::AnyCommand(const UnicodeString Command,
|
|
TCaptureOutputEvent OutputEvent)
|
|
{
|
|
|
|
#pragma warn -inl
|
|
class TOutputProxy
|
|
{
|
|
public:
|
|
__fastcall TOutputProxy(TCallSessionAction & Action, TCaptureOutputEvent OutputEvent) :
|
|
FAction(Action),
|
|
FOutputEvent(OutputEvent)
|
|
{
|
|
}
|
|
|
|
void __fastcall Output(const UnicodeString & Str, TCaptureOutputType OutputType)
|
|
{
|
|
switch (OutputType)
|
|
{
|
|
case cotOutput:
|
|
FAction.AddOutput(Str, false);
|
|
break;
|
|
case cotError:
|
|
FAction.AddOutput(Str, true);
|
|
break;
|
|
case cotExitCode:
|
|
FAction.ExitCode(StrToInt(Str));
|
|
break;
|
|
}
|
|
|
|
if (FOutputEvent != NULL)
|
|
{
|
|
FOutputEvent(Str, OutputType);
|
|
}
|
|
}
|
|
|
|
private:
|
|
TCallSessionAction & FAction;
|
|
TCaptureOutputEvent FOutputEvent;
|
|
};
|
|
#pragma warn .inl
|
|
|
|
TCallSessionAction Action(ActionLog, Command, CurrentDirectory);
|
|
TOutputProxy ProxyOutputEvent(Action, OutputEvent);
|
|
DoAnyCommand(Command, ProxyOutputEvent.Output, &Action);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoAnyCommand(const UnicodeString Command,
|
|
TCaptureOutputEvent OutputEvent, TCallSessionAction * Action)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
try
|
|
{
|
|
DirectoryModified(CurrentDirectory, false);
|
|
LogEvent(L"Executing user defined command.");
|
|
TCustomFileSystem * FileSystem = GetFileSystemForCapability(fcAnyCommand);
|
|
FileSystem->AnyCommand(Command, OutputEvent);
|
|
if (CommandSessionOpened && (FileSystem == FCommandSession->FFileSystem))
|
|
{
|
|
FCommandSession->FFileSystem->ReadCurrentDirectory();
|
|
|
|
// synchronize pwd (by purpose we lose transaction optimization here)
|
|
ChangeDirectory(FCommandSession->CurrentDirectory);
|
|
}
|
|
ReactOnCommand(fsAnyCommand);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
if (Action != NULL)
|
|
{
|
|
RollbackAction(*Action, NULL, &E);
|
|
}
|
|
if (ExceptionOnFail || (E.InheritsFrom(__classid(EFatal)))) throw;
|
|
else HandleExtendedException(&E);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoCreateLocalFile(const UnicodeString FileName,
|
|
TFileOperationProgressType * OperationProgress, HANDLE * AHandle,
|
|
bool NoConfirmation)
|
|
{
|
|
bool Result = true;
|
|
bool Done;
|
|
unsigned int CreateAttr = FILE_ATTRIBUTE_NORMAL;
|
|
do
|
|
{
|
|
*AHandle = CreateFile(ApiPath(FileName).c_str(), GENERIC_WRITE, FILE_SHARE_READ,
|
|
NULL, CREATE_ALWAYS, CreateAttr, 0);
|
|
Done = (*AHandle != INVALID_HANDLE_VALUE);
|
|
if (!Done)
|
|
{
|
|
// save the error, otherwise it gets overwritten by call to FileExists
|
|
int LastError = GetLastError();
|
|
int FileAttr = 0; // shut up
|
|
if (::FileExists(ApiPath(FileName)) &&
|
|
(((FileAttr = FileGetAttrFix(ApiPath(FileName))) & (faReadOnly | faHidden)) != 0))
|
|
{
|
|
if (FLAGSET(FileAttr, faReadOnly))
|
|
{
|
|
OperationProgress->LockUserSelections();
|
|
try
|
|
{
|
|
if (OperationProgress->BatchOverwrite == boNone)
|
|
{
|
|
Result = false;
|
|
}
|
|
else if ((OperationProgress->BatchOverwrite != boAll) && !NoConfirmation)
|
|
{
|
|
unsigned int Answer;
|
|
|
|
{
|
|
TSuspendFileOperationProgress Suspend(OperationProgress);
|
|
Answer = QueryUser(
|
|
MainInstructions(FMTLOAD(READ_ONLY_OVERWRITE, (FileName))), NULL,
|
|
qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll, 0);
|
|
}
|
|
|
|
switch (Answer) {
|
|
case qaYesToAll: OperationProgress->SetBatchOverwrite(boAll); break;
|
|
case qaCancel: OperationProgress->SetCancel(csCancel); // continue on next case
|
|
case qaNoToAll: OperationProgress->SetBatchOverwrite(boNone);
|
|
case qaNo: Result = false; break;
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
OperationProgress->UnlockUserSelections();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(FLAGSET(FileAttr, faHidden));
|
|
Result = true;
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
CreateAttr |=
|
|
FLAGMASK(FLAGSET(FileAttr, faHidden), FILE_ATTRIBUTE_HIDDEN) |
|
|
FLAGMASK(FLAGSET(FileAttr, faReadOnly), FILE_ATTRIBUTE_READONLY);
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
if (FileSetAttr(ApiPath(FileName), FileAttr & ~(faReadOnly | faHidden)) != 0)
|
|
{
|
|
RaiseLastOSError();
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (FileName)));
|
|
}
|
|
else
|
|
{
|
|
Done = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RaiseLastOSError(LastError);
|
|
}
|
|
}
|
|
}
|
|
while (!Done);
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CreateLocalFile(const UnicodeString FileName,
|
|
TFileOperationProgressType * OperationProgress, HANDLE * AHandle,
|
|
bool NoConfirmation)
|
|
{
|
|
DebugAssert(AHandle);
|
|
bool Result = true;
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
Result = DoCreateLocalFile(FileName, OperationProgress, AHandle, NoConfirmation);
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_FILE_ERROR, (FileName)));
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OpenLocalFile(const UnicodeString FileName,
|
|
unsigned int Access, int * AAttrs, HANDLE * AHandle, __int64 * ACTime,
|
|
__int64 * AMTime, __int64 * AATime, __int64 * ASize,
|
|
bool TryWriteReadOnly)
|
|
{
|
|
int Attrs = 0;
|
|
HANDLE Handle = 0;
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
UnicodeString FileNameApi = ApiPath(FileName);
|
|
Attrs = FileGetAttrFix(FileNameApi);
|
|
if (Attrs < 0)
|
|
{
|
|
RaiseLastOSError();
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
|
|
|
|
if (FLAGCLEAR(Attrs, faDirectory) || (AHandle == NULL))
|
|
{
|
|
bool NoHandle = false;
|
|
if (!TryWriteReadOnly && (Access == GENERIC_WRITE) &&
|
|
((Attrs & faReadOnly) != 0))
|
|
{
|
|
Access = GENERIC_READ;
|
|
NoHandle = true;
|
|
}
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
DWORD Flags = FLAGMASK(FLAGSET(Attrs, faDirectory), FILE_FLAG_BACKUP_SEMANTICS);
|
|
Handle = CreateFile(ApiPath(FileName).c_str(), Access,
|
|
Access == GENERIC_READ ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ,
|
|
NULL, OPEN_EXISTING, Flags, 0);
|
|
if (Handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
Handle = 0;
|
|
RaiseLastOSError();
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(OPENFILE_ERROR, (FileName)));
|
|
|
|
try
|
|
{
|
|
if (AATime || AMTime || ACTime)
|
|
{
|
|
FILETIME ATime;
|
|
FILETIME MTime;
|
|
FILETIME CTime;
|
|
|
|
// Get last file access and modification time
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
THROWOSIFFALSE(GetFileTime(Handle, &CTime, &ATime, &MTime));
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_GET_ATTRS, (FileName)));
|
|
|
|
if (ACTime)
|
|
{
|
|
*ACTime = ConvertTimestampToUnixSafe(CTime, SessionData->DSTMode);
|
|
}
|
|
if (AATime)
|
|
{
|
|
*AATime = ConvertTimestampToUnixSafe(ATime, SessionData->DSTMode);
|
|
}
|
|
if (AMTime)
|
|
{
|
|
*AMTime = ConvertTimestampToUnix(MTime, SessionData->DSTMode);
|
|
}
|
|
}
|
|
|
|
if (ASize)
|
|
{
|
|
// Get file size
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
unsigned long LSize;
|
|
unsigned long HSize;
|
|
LSize = GetFileSize(Handle, &HSize);
|
|
if ((LSize == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) RaiseLastOSError();
|
|
*ASize = (__int64(HSize) << 32) + LSize;
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_GET_ATTRS, (FileName)));
|
|
}
|
|
|
|
if ((AHandle == NULL) || NoHandle)
|
|
{
|
|
CloseHandle(Handle);
|
|
Handle = NULL;
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
CloseHandle(Handle);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
if (AAttrs) *AAttrs = Attrs;
|
|
if (AHandle) *AHandle = Handle;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::OpenLocalFile(
|
|
const UnicodeString & FileName, unsigned int Access, TLocalFileHandle & Handle, bool TryWriteReadOnly)
|
|
{
|
|
Handle.FileName = FileName;
|
|
OpenLocalFile(
|
|
FileName, Access, &Handle.Attrs, &Handle.Handle, NULL, &Handle.MTime, &Handle.ATime, &Handle.Size,
|
|
TryWriteReadOnly);
|
|
Handle.Modification = UnixToDateTime(Handle.MTime, SessionData->DSTMode);
|
|
Handle.Directory = FLAGSET(Handle.Attrs, faDirectory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoAllowLocalFileTransfer(
|
|
const UnicodeString & FileName, const TSearchRecSmart & SearchRec, const TCopyParamType * CopyParam, bool DisallowTemporaryTransferFiles)
|
|
{
|
|
TFileMasks::TParams Params;
|
|
Params.Size = SearchRec.Size;
|
|
Params.Modification = SearchRec.GetLastWriteTime();
|
|
UnicodeString BaseFileName = GetBaseFileName(FileName);
|
|
// Note that for synchronization, we do not need to check "TSynchronizeOptions::MatchesFilter" here as
|
|
// that is checked for top-level entries only and we are never top-level here.
|
|
return
|
|
CopyParam->AllowTransfer(BaseFileName, osLocal, SearchRec.IsDirectory(), Params, SearchRec.IsHidden()) &&
|
|
(!DisallowTemporaryTransferFiles || !FFileSystem->TemporaryTransferFile(FileName)) &&
|
|
(!SearchRec.IsDirectory() || !CopyParam->ExcludeEmptyDirectories ||
|
|
!IsEmptyLocalDirectory(FileName, CopyParam, DisallowTemporaryTransferFiles));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::DoAllowRemoteFileTransfer(
|
|
const TRemoteFile * File, const TCopyParamType * CopyParam, bool DisallowTemporaryTransferFiles)
|
|
{
|
|
TFileMasks::TParams MaskParams;
|
|
MaskParams.Size = File->Resolve()->Size;
|
|
MaskParams.Modification = File->Modification;
|
|
UnicodeString FullRemoteFileName = UnixExcludeTrailingBackslash(File->FullFileName);
|
|
UnicodeString BaseFileName = GetBaseFileName(FullRemoteFileName);
|
|
return
|
|
CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams, File->IsHidden) &&
|
|
(!DisallowTemporaryTransferFiles || !FFileSystem->TemporaryTransferFile(File->FileName)) &&
|
|
(!File->IsDirectory || !CopyParam->ExcludeEmptyDirectories ||
|
|
!IsEmptyRemoteDirectory(File, CopyParam, DisallowTemporaryTransferFiles));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::AllowLocalFileTransfer(
|
|
const UnicodeString & FileName, const TSearchRecSmart * SearchRec,
|
|
const TCopyParamType * CopyParam, TFileOperationProgressType * OperationProgress)
|
|
{
|
|
bool Result = true;
|
|
// optimization (though in most uses of the method, the caller actually knows TSearchRec already, so it passes it here
|
|
if (Log->Logging || !CopyParam->AllowAnyTransfer())
|
|
{
|
|
TSearchRecSmart ASearchRec;
|
|
if (SearchRec == NULL)
|
|
{
|
|
if (CopyParam->OnTransferIn != NULL)
|
|
{
|
|
ASearchRec.Clear();
|
|
}
|
|
else
|
|
{
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
if (!FileSearchRec(FileName, ASearchRec))
|
|
{
|
|
RaiseLastOSError();
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
|
|
}
|
|
SearchRec = &ASearchRec;
|
|
}
|
|
|
|
if (!DoAllowLocalFileTransfer(FileName, *SearchRec, CopyParam, false))
|
|
{
|
|
LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
|
|
Result = false;
|
|
}
|
|
else if (CopyParam->SkipTransfer(FileName, SearchRec->IsDirectory()))
|
|
{
|
|
OperationProgress->AddSkippedFileSize(SearchRec->Size);
|
|
Result = false;
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
LogFileDetails(FileName, SearchRec->GetLastWriteTime(), SearchRec->Size);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::MakeLocalFileList(
|
|
const UnicodeString & FileName, const TSearchRecSmart & Rec, void * Param)
|
|
{
|
|
TMakeLocalFileListParams & Params = *static_cast<TMakeLocalFileListParams*>(Param);
|
|
|
|
if (Rec.IsDirectory() && Params.Recursive)
|
|
{
|
|
ProcessLocalDirectory(FileName, MakeLocalFileList, &Params);
|
|
}
|
|
|
|
if (!Rec.IsDirectory() || Params.IncludeDirs)
|
|
{
|
|
Params.FileList->Add(FileName);
|
|
if (Params.FileTimes != NULL)
|
|
{
|
|
Params.FileTimes->push_back(const_cast<TSearchRecSmart &>(Rec).TimeStamp);
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CalculateLocalFileSize(
|
|
const UnicodeString & FileName, const TSearchRecSmart & Rec, /*TCalculateSizeParams*/ void * Params)
|
|
{
|
|
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Params);
|
|
|
|
if (!TryStartOperationWithFile(FileName, foCalculateSize))
|
|
{
|
|
AParams->Result = false;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
if ((AParams->CopyParam == NULL) ||
|
|
DoAllowLocalFileTransfer(FileName, Rec, AParams->CopyParam, false))
|
|
{
|
|
int CollectionIndex = -1;
|
|
if (AParams->Files != NULL)
|
|
{
|
|
UnicodeString FullFileName = ::ExpandFileName(FileName);
|
|
CollectionIndex = AParams->Files->Add(FullFileName, NULL, Rec.IsDirectory());
|
|
}
|
|
|
|
if (!Rec.IsDirectory())
|
|
{
|
|
AParams->Size += Rec.Size;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
ProcessLocalDirectory(FileName, CalculateLocalFileSize, Params);
|
|
}
|
|
catch (...)
|
|
{
|
|
if (CollectionIndex >= 0)
|
|
{
|
|
AParams->Files->DidNotRecurse(CollectionIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CalculateLocalFilesSize(TStrings * FileList,
|
|
__int64 & Size, const TCopyParamType * CopyParam, bool AllowDirs, TStrings * Files,
|
|
TCalculatedSizes * CalculatedSizes)
|
|
{
|
|
bool Result = false;
|
|
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
|
|
TOnceDoneOperation OnceDoneOperation = odoIdle;
|
|
OperationStart(OperationProgress, foCalculateSize, osLocal, FileList->Count);
|
|
try
|
|
{
|
|
TCalculateSizeParams Params;
|
|
Params.Size = 0;
|
|
Params.Params = 0;
|
|
Params.CopyParam = CopyParam;
|
|
Params.Files = NULL;
|
|
Params.Result = true;
|
|
|
|
UnicodeString LastDirPath;
|
|
for (int Index = 0; Params.Result && (Index < FileList->Count); Index++)
|
|
{
|
|
UnicodeString FileName = FileList->Strings[Index];
|
|
TSearchRecSmart Rec;
|
|
if (FileSearchRec(FileName, Rec))
|
|
{
|
|
if (Rec.IsDirectory() && !AllowDirs)
|
|
{
|
|
Params.Result = false;
|
|
}
|
|
else
|
|
{
|
|
if (Files != NULL)
|
|
{
|
|
UnicodeString FullFileName = ::ExpandFileName(FileName);
|
|
UnicodeString DirPath = ExtractFilePath(FullFileName);
|
|
if (DirPath != LastDirPath)
|
|
{
|
|
Params.Files = new TCollectedFileList();
|
|
LastDirPath = DirPath;
|
|
Files->AddObject(LastDirPath, Params.Files);
|
|
}
|
|
}
|
|
|
|
__int64 PrevSize = Params.Size;
|
|
|
|
CalculateLocalFileSize(FileName, Rec, &Params);
|
|
|
|
if (CalculatedSizes != NULL)
|
|
{
|
|
__int64 Size = Params.Size - PrevSize;
|
|
CalculatedSizes->push_back(Size);
|
|
}
|
|
|
|
OperationFinish(&OperationProgress, FileList->Objects[Index], FileName, true, OnceDoneOperation);
|
|
}
|
|
}
|
|
}
|
|
|
|
Size = Params.Size;
|
|
Result = Params.Result;
|
|
}
|
|
__finally
|
|
{
|
|
OperationStop(OperationProgress);
|
|
}
|
|
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
LogEvent(FORMAT(L"Size of %d local files/folders calculated as %s", (FileList->Count, IntToStr(Size))));
|
|
}
|
|
|
|
if (OnceDoneOperation != odoIdle)
|
|
{
|
|
CloseOnCompletion(OnceDoneOperation);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
struct TSynchronizeFileData
|
|
{
|
|
bool Modified;
|
|
bool New;
|
|
bool IsDirectory;
|
|
TSynchronizeChecklist::TItem::TFileInfo Info;
|
|
TSynchronizeChecklist::TItem::TFileInfo MatchingFile2;
|
|
TRemoteFile * MatchingRemoteFile2;
|
|
int MatchingRemoteFile2ImageIndex;
|
|
FILETIME LocalLastWriteTime;
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
const int sfFirstLevel = 0x01;
|
|
struct TSynchronizeData
|
|
{
|
|
UnicodeString Directory1;
|
|
UnicodeString Directory2;
|
|
TTerminal::TSynchronizeMode Mode;
|
|
const TCopyParamType * CopyParam;
|
|
int Params;
|
|
TSynchronizeDirectory OnSynchronizeDirectory;
|
|
TSynchronizeOptions * Options;
|
|
TStringList * LeftFileList;
|
|
int Flags;
|
|
TSynchronizeChecklist * Checklist;
|
|
|
|
void ClearLevelSpecific()
|
|
{
|
|
LeftFileList = NULL;
|
|
}
|
|
|
|
TSynchronizeData CloneFor(const UnicodeString & ADirectory1, const UnicodeString & ADirectory2)
|
|
{
|
|
TSynchronizeData Result = *this;
|
|
Result.ClearLevelSpecific();
|
|
Result.Directory1 = ADirectory1;
|
|
Result.Directory2 = ADirectory2;
|
|
Result.Flags = (Result.Flags & ~sfFirstLevel);
|
|
return Result;
|
|
}
|
|
|
|
void IncFiles()
|
|
{
|
|
// Can be NULL in scripting
|
|
if (Options != NULL)
|
|
{
|
|
Options->Files++;
|
|
}
|
|
}
|
|
|
|
bool MatchesFilter(const UnicodeString & FileName)
|
|
{
|
|
return
|
|
FLAGCLEAR(Flags, sfFirstLevel) ||
|
|
(Options == NULL) ||
|
|
Options->MatchesFilter(FileName);
|
|
}
|
|
|
|
TSynchronizeFileData * MatchLeftFile(const UnicodeString & FileName)
|
|
{
|
|
int Index = LeftFileList->IndexOf(FileName);
|
|
TSynchronizeFileData * Result = NULL;
|
|
if (Index >= 0)
|
|
{
|
|
Result = reinterpret_cast<TSynchronizeFileData *>(LeftFileList->Objects[Index]);
|
|
Result->New = false;
|
|
}
|
|
return Result;
|
|
}
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
TSynchronizeChecklist * TTerminal::SynchronizeCollect(
|
|
const UnicodeString & Directory1, const UnicodeString & Directory2,
|
|
TSynchronizeMode Mode, const TCopyParamType * CopyParam, int Params,
|
|
TSynchronizeDirectory OnSynchronizeDirectory,
|
|
TSynchronizeOptions * Options)
|
|
{
|
|
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor, false);
|
|
|
|
std::unique_ptr<TSynchronizeChecklist> Checklist(new TSynchronizeChecklist());
|
|
|
|
TSynchronizeData Data;
|
|
Data.Directory1 = Directory1;
|
|
Data.Directory2 = Directory2;
|
|
Data.Mode = Mode;
|
|
Data.CopyParam = CopyParam;
|
|
Data.Params = Params;
|
|
Data.OnSynchronizeDirectory = OnSynchronizeDirectory;
|
|
Data.Options = Options;
|
|
Data.Flags = sfFirstLevel;
|
|
Data.Checklist = Checklist.get();
|
|
Data.ClearLevelSpecific();
|
|
|
|
DoSynchronizeCollectDirectory(Data);
|
|
Checklist->Sort();
|
|
|
|
return Checklist.release();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
static void __fastcall AddFlagName(UnicodeString & ParamsStr, int & Params, int Param, const UnicodeString & Name)
|
|
{
|
|
if (FLAGSET(Params, Param))
|
|
{
|
|
AddToList(ParamsStr, Name, ", ");
|
|
}
|
|
Params &= ~Param;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::SynchronizeModeStr(TSynchronizeMode Mode)
|
|
{
|
|
UnicodeString ModeStr;
|
|
switch (Mode)
|
|
{
|
|
case smRemote:
|
|
ModeStr = L"Remote";
|
|
break;
|
|
case smLocal:
|
|
ModeStr = L"Local";
|
|
break;
|
|
case smBoth:
|
|
ModeStr = L"Both";
|
|
break;
|
|
default:
|
|
ModeStr = L"Unknown";
|
|
break;
|
|
}
|
|
return ModeStr;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::SynchronizeParamsStr(int Params)
|
|
{
|
|
UnicodeString ParamsStr;
|
|
AddFlagName(ParamsStr, Params, spDelete, L"Delete");
|
|
AddFlagName(ParamsStr, Params, spNoConfirmation, L"NoConfirmation");
|
|
AddFlagName(ParamsStr, Params, spExistingOnly, L"ExistingOnly");
|
|
AddFlagName(ParamsStr, Params, spNoRecurse, L"NoRecurse");
|
|
AddFlagName(ParamsStr, Params, spUseCache, L"UseCache");
|
|
AddFlagName(ParamsStr, Params, spDelayProgress, L"DelayProgress");
|
|
AddFlagName(ParamsStr, Params, spPreviewChanges, L"*PreviewChanges"); // GUI only
|
|
AddFlagName(ParamsStr, Params, spSubDirs, L"SubDirs");
|
|
AddFlagName(ParamsStr, Params, spTimestamp, L"Timestamp");
|
|
AddFlagName(ParamsStr, Params, spNotByTime, L"NotByTime");
|
|
AddFlagName(ParamsStr, Params, spBySize, L"BySize");
|
|
AddFlagName(ParamsStr, Params, spByChecksum, L"ByChecksum");
|
|
AddFlagName(ParamsStr, Params, spCaseSensitive, L"CaseSensitive");
|
|
AddFlagName(ParamsStr, Params, spSelectedOnly, L"*SelectedOnly"); // GUI only
|
|
AddFlagName(ParamsStr, Params, spMirror, L"Mirror");
|
|
Params &= ~spLocalLocal; // internal
|
|
if (Params > 0)
|
|
{
|
|
AddToList(ParamsStr, FORMAT(L"0x%x", (int(Params))), L", ");
|
|
}
|
|
return ParamsStr;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::LocalFindFirstLoop(const UnicodeString & Path, TSearchRecChecked & SearchRec)
|
|
{
|
|
bool Result;
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
const int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
|
|
Result = (FindFirstChecked(Path, FindAttrs, SearchRec) == 0);
|
|
}
|
|
FILE_OPERATION_LOOP_END(MainInstructions(FMTLOAD(LIST_DIR_ERROR, (Path))));
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::LocalFindNextLoop(TSearchRecChecked & SearchRec)
|
|
{
|
|
bool Result;
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
Result = (FindNextChecked(SearchRec) == 0);
|
|
}
|
|
FILE_OPERATION_LOOP_END(MainInstructions(FMTLOAD(LIST_DIR_ERROR, (SearchRec.Path))));
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::IsEmptyLocalDirectory(
|
|
const UnicodeString & Path, const TCopyParamType * CopyParam, bool DisallowTemporaryTransferFiles)
|
|
{
|
|
UnicodeString Contents;
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
LogEvent(FORMAT(L"Checking if local directory \"%s\" is empty", (Path)));
|
|
}
|
|
|
|
TSearchRecOwned SearchRec;
|
|
if (LocalFindFirstLoop(IncludeTrailingBackslash(Path) + L"*.*", SearchRec))
|
|
{
|
|
do
|
|
{
|
|
UnicodeString FullLocalFileName = SearchRec.GetFilePath();
|
|
if (SearchRec.IsRealFile() &&
|
|
DoAllowLocalFileTransfer(FullLocalFileName, SearchRec, CopyParam, true))
|
|
{
|
|
if (!SearchRec.IsDirectory() ||
|
|
!IsEmptyLocalDirectory(FullLocalFileName, CopyParam, DisallowTemporaryTransferFiles))
|
|
{
|
|
Contents = SearchRec.Name;
|
|
}
|
|
}
|
|
}
|
|
while (Contents.IsEmpty() && LocalFindNextLoop(SearchRec));
|
|
}
|
|
|
|
if (Configuration->ActualLogProtocol >= 1)
|
|
{
|
|
if (Contents.IsEmpty())
|
|
{
|
|
LogEvent(FORMAT(L"Local directory \"%s\" is empty", (Path)));
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Local directory \"%s\" is not empty, it contains \"%s\"", (Path, Contents)));
|
|
}
|
|
}
|
|
|
|
return Contents.IsEmpty();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void DestroyLocalFileList(TStringList * LocalFileList)
|
|
{
|
|
// Factored out to workaround Clang ICE
|
|
if (LocalFileList != NULL)
|
|
{
|
|
for (int Index = 0; Index < LocalFileList->Count; Index++)
|
|
{
|
|
TSynchronizeFileData * FileData = reinterpret_cast<TSynchronizeFileData*>(LocalFileList->Objects[Index]);
|
|
delete FileData;
|
|
}
|
|
delete LocalFileList;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::DoSynchronizeCollectDirectory(TSynchronizeData Data)
|
|
{
|
|
Data.Directory1 = IncludeTrailingBackslash(Data.Directory1);
|
|
Data.Directory2 = UniversalIncludeTrailingBackslash(FLAGCLEAR(Data.Params, spLocalLocal), Data.Directory2);
|
|
|
|
LogEvent(FORMAT(
|
|
L"Collecting synchronization list for '%s' and '%s', mode = %s, params = 0x%x (%s), file mask = '%s'",
|
|
(Data.Directory1, Data.Directory2, SynchronizeModeStr(Data.Mode), int(Data.Params), SynchronizeParamsStr(Data.Params),
|
|
Data.CopyParam->IncludeFileMask.Masks)));
|
|
|
|
if (FLAGCLEAR(Data.Params, spDelayProgress))
|
|
{
|
|
DoSynchronizeProgress(Data, true);
|
|
}
|
|
|
|
try
|
|
{
|
|
Data.LeftFileList = CreateSortedStringList(FLAGSET(Data.Params, spCaseSensitive));
|
|
|
|
TSearchRecOwned SearchRec;
|
|
if (LocalFindFirstLoop(Data.Directory1 + L"*.*", SearchRec))
|
|
{
|
|
do
|
|
{
|
|
UnicodeString FileName = SearchRec.Name;
|
|
UnicodeString FullLocalFileName = SearchRec.GetFilePath();
|
|
UnicodeString RemoteFileName = ChangeFileName(Data.CopyParam, FileName, osLocal, false);
|
|
if (SearchRec.IsRealFile() &&
|
|
DoAllowLocalFileTransfer(FullLocalFileName, SearchRec, Data.CopyParam, FLAGCLEAR(Data.Params, spLocalLocal)) &&
|
|
(Data.MatchesFilter(FileName) || Data.MatchesFilter(RemoteFileName)))
|
|
{
|
|
TSynchronizeFileData * FileData = new TSynchronizeFileData;
|
|
|
|
FileData->IsDirectory = SearchRec.IsDirectory();
|
|
FileData->Info.FileName = FileName;
|
|
FileData->Info.Directory = Data.Directory1;
|
|
FileData->Info.Modification = SearchRec.GetLastWriteTime();
|
|
FileData->Info.ModificationFmt = mfFull;
|
|
FileData->Info.Size = SearchRec.Size;
|
|
FileData->LocalLastWriteTime = SearchRec.FindData.ftLastWriteTime;
|
|
FileData->New = true;
|
|
FileData->Modified = false;
|
|
Data.LeftFileList->AddObject(FileName, reinterpret_cast<TObject*>(FileData));
|
|
LogEvent(0, FORMAT(L"Left file %s included to synchronization",
|
|
(FormatFileDetailsForLog(FullLocalFileName, SearchRec.GetLastWriteTime(), SearchRec.Size))));
|
|
}
|
|
else
|
|
{
|
|
LogEvent(0, FORMAT(L"Left file %s excluded from synchronization",
|
|
(FormatFileDetailsForLog(FullLocalFileName, SearchRec.GetLastWriteTime(), SearchRec.Size))));
|
|
}
|
|
|
|
}
|
|
while (LocalFindNextLoop(SearchRec));
|
|
|
|
SearchRec.Close();
|
|
|
|
// can we expect that ProcessDirectory would take so little time
|
|
// that we can postpone showing progress window until anything actually happens?
|
|
bool Directory2ShouldBeQuick =
|
|
FLAGSET(Data.Params, spLocalLocal) ||
|
|
(FLAGSET(Data.Params, spUseCache) && SessionData->CacheDirectories && FDirectoryCache->HasFileList(Data.Directory2));
|
|
|
|
if (!Directory2ShouldBeQuick && FLAGSET(Data.Params, spDelayProgress))
|
|
{
|
|
DoSynchronizeProgress(Data, true);
|
|
}
|
|
|
|
if (FLAGCLEAR(Data.Params, spLocalLocal))
|
|
{
|
|
ProcessDirectory(Data.Directory2, SynchronizeCollectFile, &Data, FLAGSET(Data.Params, spUseCache));
|
|
}
|
|
else
|
|
{
|
|
// Not using ProcessLocalDirectory for a better error handling
|
|
TSearchRecOwned SearchRec;
|
|
if (LocalFindFirstLoop(Data.Directory2 + L"*.*", SearchRec))
|
|
{
|
|
do
|
|
{
|
|
if (SearchRec.IsRealFile())
|
|
{
|
|
SynchronizeCollectLocalFile(SearchRec.GetFilePath(), SearchRec, &Data);
|
|
}
|
|
}
|
|
while (LocalFindNextLoop(SearchRec));
|
|
}
|
|
}
|
|
|
|
TSynchronizeFileData * FileData;
|
|
for (int Index = 0; Index < Data.LeftFileList->Count; Index++)
|
|
{
|
|
FileData = reinterpret_cast<TSynchronizeFileData *>
|
|
(Data.LeftFileList->Objects[Index]);
|
|
// add local file either if we are going to upload it
|
|
// (i.e. if it is updated or we want to upload even new files)
|
|
// or if we are going to delete it (i.e. all "new"=obsolete files)
|
|
bool Modified = (FileData->Modified && ((Data.Mode == smBoth) || (Data.Mode == smRemote)));
|
|
bool New = (FileData->New &&
|
|
((Data.Mode == smLocal) ||
|
|
(((Data.Mode == smBoth) || (Data.Mode == smRemote)) && FLAGCLEAR(Data.Params, spTimestamp))));
|
|
|
|
if (New)
|
|
{
|
|
LogEvent(FORMAT(L"Left file %s is new",
|
|
(FormatFileDetailsForLog(FileData->Info.Directory + FileData->Info.FileName,
|
|
FileData->Info.Modification, FileData->Info.Size))));
|
|
}
|
|
|
|
if (Modified || New)
|
|
{
|
|
TSynchronizeChecklist::TItem * ChecklistItem = new TSynchronizeChecklist::TItem();
|
|
try
|
|
{
|
|
ChecklistItem->IsDirectory = FileData->IsDirectory;
|
|
|
|
ChecklistItem->Info1 = FileData->Info;
|
|
ChecklistItem->FLocalLastWriteTime = FileData->LocalLastWriteTime;
|
|
|
|
if (Modified)
|
|
{
|
|
DebugAssert(!FileData->MatchingFile2.Directory.IsEmpty());
|
|
ChecklistItem->Info2 = FileData->MatchingFile2;
|
|
ChecklistItem->ImageIndex = FileData->MatchingRemoteFile2ImageIndex;
|
|
ChecklistItem->RemoteFile = FileData->MatchingRemoteFile2;
|
|
}
|
|
else
|
|
{
|
|
ChecklistItem->Info2.Directory = Data.Directory2;
|
|
}
|
|
|
|
if ((Data.Mode == smBoth) || (Data.Mode == smRemote))
|
|
{
|
|
ChecklistItem->Action =
|
|
(Modified ? TSynchronizeChecklist::saUploadUpdate : TSynchronizeChecklist::saUploadNew);
|
|
ChecklistItem->Checked =
|
|
(Modified || FLAGCLEAR(Data.Params, spExistingOnly)) &&
|
|
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data.Params, spNoRecurse) ||
|
|
FLAGSET(Data.Params, spSubDirs));
|
|
}
|
|
else if ((Data.Mode == smLocal) && FLAGCLEAR(Data.Params, spTimestamp))
|
|
{
|
|
ChecklistItem->Action = TSynchronizeChecklist::saDeleteLocal;
|
|
ChecklistItem->Checked =
|
|
FLAGSET(Data.Params, spDelete) &&
|
|
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data.Params, spNoRecurse) ||
|
|
FLAGSET(Data.Params, spSubDirs));
|
|
}
|
|
|
|
if (ChecklistItem->Action != TSynchronizeChecklist::saNone)
|
|
{
|
|
Data.Checklist->Add(ChecklistItem);
|
|
ChecklistItem = NULL;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
delete ChecklistItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FileData->Modified)
|
|
{
|
|
delete FileData->MatchingRemoteFile2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
DestroyLocalFileList(Data.LeftFileList);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SynchronizeCollectFile(const UnicodeString FileName,
|
|
const TRemoteFile * File, /*TSynchronizeData*/ void * Param)
|
|
{
|
|
try
|
|
{
|
|
DoSynchronizeCollectFile(FileName, File, Param);
|
|
}
|
|
catch(ESkipFile & E)
|
|
{
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::IsEmptyRemoteDirectory(
|
|
const TRemoteFile * File, const TCopyParamType * ACopyParam, bool DisallowTemporaryTransferFiles)
|
|
{
|
|
TCalculateSizeStats Stats;
|
|
|
|
TCopyParamType CopyParam(*ACopyParam);
|
|
CopyParam.ExcludeEmptyDirectories = false; // to avoid endless recursion
|
|
|
|
TCalculateSizeParams Params;
|
|
Params.Params = csStopOnFirstFile | csIgnoreErrors | FLAGMASK(DisallowTemporaryTransferFiles, csDisallowTemporaryTransferFiles);
|
|
Params.CopyParam = &CopyParam;
|
|
Params.Stats = &Stats;
|
|
|
|
DoCalculateDirectorySize(UnixExcludeTrailingBackslash(File->FullFileName), &Params);
|
|
|
|
return Params.Result && (Stats.Files == 0);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CollectCalculatedChecksum(
|
|
const UnicodeString & DebugUsedArg(FileName), const UnicodeString & DebugUsedArg(Alg), const UnicodeString & Hash)
|
|
{
|
|
DebugAssert(FCollectedCalculatedChecksum.IsEmpty());
|
|
FCollectedCalculatedChecksum = Hash;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString TTerminal::CalculateLocalFileChecksum(const UnicodeString & FileName, const UnicodeString & Alg)
|
|
{
|
|
UnicodeString Result;
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
std::unique_ptr<THandleStream> Stream(TSafeHandleStream::CreateFromFile(FileName, fmOpenRead | fmShareDenyWrite));
|
|
Result = CalculateFileChecksum(Stream.get(), Alg);
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CHECKSUM_ERROR, (FileName)));
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::SameFileChecksum(
|
|
const UnicodeString & LeftFileName, const UnicodeString & RightFileName, const TRemoteFile * RightFile)
|
|
{
|
|
UnicodeString DefaultAlg = Sha256ChecksumAlg;
|
|
UnicodeString Algs =
|
|
DefaultStr(Configuration->SynchronizationChecksumAlgs, DefaultAlg + L"," + Sha1ChecksumAlg);
|
|
std::unique_ptr<TStrings> SupportedAlgs(CreateSortedStringList());
|
|
GetSupportedChecksumAlgs(SupportedAlgs.get());
|
|
UnicodeString Alg;
|
|
while (Alg.IsEmpty() && !Algs.IsEmpty())
|
|
{
|
|
UnicodeString A = CutToChar(Algs, L',', true);
|
|
|
|
if (SupportedAlgs->IndexOf(A) >= 0)
|
|
{
|
|
Alg = A;
|
|
}
|
|
}
|
|
|
|
if (Alg.IsEmpty())
|
|
{
|
|
Alg = DefaultAlg;
|
|
}
|
|
|
|
UnicodeString RightChecksum;
|
|
if (RightFile == NULL)
|
|
{
|
|
RightChecksum = CalculateLocalFileChecksum(RightFileName, Alg);
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<TStrings> FileList(new TStringList());
|
|
FileList->AddObject(RightFile->FullFileName, const_cast<TRemoteFile *>(RightFile));
|
|
DebugAssert(FCollectedCalculatedChecksum.IsEmpty());
|
|
FCollectedCalculatedChecksum = EmptyStr;
|
|
CalculateFilesChecksum(Alg, FileList.get(), CollectCalculatedChecksum);
|
|
RightChecksum = FCollectedCalculatedChecksum;
|
|
FCollectedCalculatedChecksum = EmptyStr;
|
|
}
|
|
|
|
UnicodeString LeftChecksum = CalculateLocalFileChecksum(LeftFileName, Alg);
|
|
|
|
return SameText(RightChecksum, LeftChecksum);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::SynchronizedFileCheckModified(
|
|
TSynchronizeData * Data, std::unique_ptr<TSynchronizeChecklist::TItem> & ChecklistItem,
|
|
const UnicodeString & FullLeftFileName, TSynchronizeFileData * LocalData,
|
|
const UnicodeString & FullRightFileName, const TRemoteFile * RightFile)
|
|
{
|
|
bool Modified = false;
|
|
bool LeftModified = false;
|
|
// for spTimestamp+spBySize require that the file sizes are the same
|
|
// before comparing file time
|
|
int TimeCompare;
|
|
if (FLAGCLEAR(Data->Params, spNotByTime) &&
|
|
(FLAGCLEAR(Data->Params, spTimestamp) ||
|
|
FLAGCLEAR(Data->Params, spBySize) ||
|
|
(ChecklistItem->Info1.Size == ChecklistItem->Info2.Size)))
|
|
{
|
|
TimeCompare =
|
|
CompareFileTime(ChecklistItem->Info1.Modification, ChecklistItem->Info2.Modification);
|
|
}
|
|
else
|
|
{
|
|
TimeCompare = 0;
|
|
}
|
|
if (TimeCompare < 0)
|
|
{
|
|
if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) ||
|
|
(Data->Mode == smBoth) || (Data->Mode == smLocal))
|
|
{
|
|
Modified = true;
|
|
}
|
|
else
|
|
{
|
|
LeftModified = true;
|
|
}
|
|
}
|
|
else if (TimeCompare > 0)
|
|
{
|
|
if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) ||
|
|
(Data->Mode == smBoth) || (Data->Mode == smRemote))
|
|
{
|
|
LeftModified = true;
|
|
}
|
|
else
|
|
{
|
|
Modified = true;
|
|
}
|
|
}
|
|
else if (FLAGSET(Data->Params, spBySize) &&
|
|
(ChecklistItem->Info1.Size != ChecklistItem->Info2.Size) &&
|
|
FLAGCLEAR(Data->Params, spTimestamp))
|
|
{
|
|
Modified = true;
|
|
LeftModified = true;
|
|
}
|
|
else if (FLAGSET(Data->Params, spByChecksum) &&
|
|
FLAGCLEAR(Data->Params, spTimestamp) &&
|
|
!SameFileChecksum(FullLeftFileName, FullRightFileName, RightFile) &&
|
|
FLAGCLEAR(Data->Params, spTimestamp))
|
|
{
|
|
Modified = true;
|
|
LeftModified = true;
|
|
}
|
|
|
|
const TRemoteFile * RightLinkedFile = (RightFile != NULL) ? RightFile->LinkedFile : NULL;
|
|
|
|
if (LeftModified)
|
|
{
|
|
LocalData->Modified = true;
|
|
LocalData->MatchingFile2 = ChecklistItem->Info2;
|
|
LocalData->MatchingRemoteFile2ImageIndex = ChecklistItem->ImageIndex;
|
|
// we need this for custom commands over checklist only,
|
|
// not for sync itself
|
|
LocalData->MatchingRemoteFile2 = (RightFile != NULL) ? RightFile->Duplicate() : NULL;
|
|
LogEvent(FORMAT(L"Left file %s is modified comparing to right file %s",
|
|
(FormatFileDetailsForLog(FullLeftFileName, LocalData->Info.Modification, LocalData->Info.Size),
|
|
FormatFileDetailsForLog(FullRightFileName, ChecklistItem->Info2.Modification, ChecklistItem->Info2.Size, RightLinkedFile))));
|
|
}
|
|
|
|
if (Modified)
|
|
{
|
|
LogEvent(FORMAT(L"Right file %s is modified comparing to left file %s",
|
|
(FormatFileDetailsForLog(FullRightFileName, ChecklistItem->Info2.Modification, ChecklistItem->Info2.Size, RightLinkedFile),
|
|
FormatFileDetailsForLog(FullLeftFileName, LocalData->Info.Modification, LocalData->Info.Size))));
|
|
SynchronizedFileNewOrModified(Data, ChecklistItem, RightFile, false);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::SynchronizedFileNew(
|
|
TSynchronizeData * Data, std::unique_ptr<TSynchronizeChecklist::TItem> & ChecklistItem,
|
|
const UnicodeString & FullRightFileName, const TRemoteFile * RightFile)
|
|
{
|
|
ChecklistItem->Info1.Directory = Data->Directory1;
|
|
const TRemoteFile * RightLinkedFile = (RightFile != NULL) ? RightFile->LinkedFile : NULL;
|
|
LogEvent(FORMAT(L"Right file %s is new",
|
|
(FormatFileDetailsForLog(FullRightFileName, ChecklistItem->Info2.Modification, ChecklistItem->Info2.Size, RightLinkedFile))));
|
|
SynchronizedFileNewOrModified(Data, ChecklistItem, RightFile, true);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::SynchronizedFileNewOrModified(
|
|
TSynchronizeData * Data, std::unique_ptr<TSynchronizeChecklist::TItem> & ChecklistItem, const TRemoteFile * File, bool New)
|
|
{
|
|
// download the file if it changed or is new and we want to have it locally
|
|
if ((Data->Mode == smBoth) || (Data->Mode == smLocal))
|
|
{
|
|
if (FLAGCLEAR(Data->Params, spTimestamp) || !New)
|
|
{
|
|
ChecklistItem->Action =
|
|
(!New ? TSynchronizeChecklist::saDownloadUpdate : TSynchronizeChecklist::saDownloadNew);
|
|
ChecklistItem->Checked =
|
|
(!New || FLAGCLEAR(Data->Params, spExistingOnly)) &&
|
|
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) ||
|
|
FLAGSET(Data->Params, spSubDirs));
|
|
}
|
|
}
|
|
else if ((Data->Mode == smRemote) && New)
|
|
{
|
|
if (FLAGCLEAR(Data->Params, spTimestamp))
|
|
{
|
|
ChecklistItem->Action = TSynchronizeChecklist::saDeleteRemote;
|
|
ChecklistItem->Checked =
|
|
FLAGSET(Data->Params, spDelete) &&
|
|
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) ||
|
|
FLAGSET(Data->Params, spSubDirs));
|
|
}
|
|
}
|
|
|
|
if (ChecklistItem->Action != TSynchronizeChecklist::saNone)
|
|
{
|
|
if (File != NULL)
|
|
{
|
|
ChecklistItem->RemoteFile = File->Duplicate();
|
|
}
|
|
Data->Checklist->Add(ChecklistItem.release());
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::DoFilesMatch(const TSynchronizeFileData * LeftItem, TSynchronizeChecklist::TItem * RightItem)
|
|
{
|
|
bool Result = (LeftItem->IsDirectory == RightItem->IsDirectory);
|
|
if (!Result)
|
|
{
|
|
LogEvent(FORMAT(L"%s is directory on one side, but file on the another",
|
|
(RightItem->Info2.FileName)));
|
|
}
|
|
else
|
|
{
|
|
if (!RightItem->IsDirectory)
|
|
{
|
|
RightItem->Info1 = LeftItem->Info;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoSynchronizeCollectFile(const UnicodeString FileName,
|
|
const TRemoteFile * File, /*TSynchronizeData*/ void * Param)
|
|
{
|
|
DebugUsedParam(FileName);
|
|
TSynchronizeData * Data = static_cast<TSynchronizeData *>(Param);
|
|
Data->IncFiles();
|
|
|
|
UnicodeString LocalFileName = ChangeFileName(Data->CopyParam, File->FileName, osRemote, false);
|
|
UnicodeString FullRemoteFileName = UnixExcludeTrailingBackslash(File->FullFileName);
|
|
if (DoAllowRemoteFileTransfer(File, Data->CopyParam, true) &&
|
|
(Data->MatchesFilter(File->FileName) || Data->MatchesFilter(LocalFileName)))
|
|
{
|
|
std::unique_ptr<TSynchronizeChecklist::TItem> ChecklistItem(new TSynchronizeChecklist::TItem());
|
|
ChecklistItem->IsDirectory = File->IsDirectory;
|
|
ChecklistItem->ImageIndex = File->IconIndex;
|
|
|
|
ChecklistItem->Info2.FileName = File->FileName;
|
|
ChecklistItem->Info2.Directory = Data->Directory2;
|
|
ChecklistItem->Info2.Modification = File->Modification;
|
|
ChecklistItem->Info2.ModificationFmt = File->ModificationFmt;
|
|
ChecklistItem->Info2.Size = File->Resolve()->Size;
|
|
|
|
if (File->IsDirectory && !CanRecurseToDirectory(File))
|
|
{
|
|
LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (File->FileName)));
|
|
}
|
|
else
|
|
{
|
|
TSynchronizeFileData * LocalData = Data->MatchLeftFile(LocalFileName);
|
|
if ((LocalData != NULL) && DoFilesMatch(LocalData, ChecklistItem.get()))
|
|
{
|
|
UnicodeString FullLocalFileName = LocalData->Info.Directory + LocalData->Info.FileName;
|
|
|
|
if (!ChecklistItem->IsDirectory)
|
|
{
|
|
ChecklistItem->Info1.Modification =
|
|
ReduceDateTimePrecision(ChecklistItem->Info1.Modification, File->ModificationFmt);
|
|
|
|
SynchronizedFileCheckModified(Data, ChecklistItem, FullLocalFileName, LocalData, FullRemoteFileName, File);
|
|
}
|
|
else if (FLAGCLEAR(Data->Params, spNoRecurse))
|
|
{
|
|
DoSynchronizeCollectDirectory(Data->CloneFor(FullLocalFileName, FullRemoteFileName));
|
|
}
|
|
}
|
|
else if (LocalData == NULL)
|
|
{
|
|
SynchronizedFileNew(Data, ChecklistItem, FullRemoteFileName, File);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogEvent(0, FORMAT(L"Right file %s excluded from synchronization",
|
|
(FormatFileDetailsForLog(FullRemoteFileName, File->Modification, File->Size, File->LinkedFile))));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::SynchronizeCollectLocalFile(
|
|
const UnicodeString & FileName, const TSearchRecSmart & Rec, TSynchronizeData * Data)
|
|
{
|
|
try
|
|
{
|
|
DoSynchronizeCollectLocalFile(FileName, Rec, Data);
|
|
}
|
|
catch (ESkipFile & E)
|
|
{
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::DoSynchronizeCollectLocalFile(
|
|
const UnicodeString & FileName, const TSearchRecSmart & SearchRec, TSynchronizeData * Data)
|
|
{
|
|
DebugUsedParam(FileName);
|
|
Data->IncFiles();
|
|
|
|
if (DoAllowLocalFileTransfer(FileName, SearchRec, Data->CopyParam, false) &&
|
|
Data->MatchesFilter(SearchRec.Name))
|
|
{
|
|
std::unique_ptr<TSynchronizeChecklist::TItem> ChecklistItem(new TSynchronizeChecklist::TItem());
|
|
ChecklistItem->IsDirectory = SearchRec.IsDirectory();
|
|
|
|
ChecklistItem->Info2.FileName = SearchRec.Name;
|
|
ChecklistItem->Info2.Directory = Data->Directory2;
|
|
ChecklistItem->Info2.Modification = SearchRec.GetLastWriteTime();
|
|
ChecklistItem->Info2.Size = SearchRec.Size;
|
|
|
|
TSynchronizeFileData * LeftData = Data->MatchLeftFile(SearchRec.Name);
|
|
if ((LeftData != NULL) && DoFilesMatch(LeftData, ChecklistItem.get()))
|
|
{
|
|
UnicodeString FullLeftFileName = LeftData->Info.Directory + LeftData->Info.FileName;
|
|
|
|
if (!ChecklistItem->IsDirectory)
|
|
{
|
|
SynchronizedFileCheckModified(Data, ChecklistItem, FullLeftFileName, LeftData, FileName, NULL);
|
|
}
|
|
else if (FLAGCLEAR(Data->Params, spNoRecurse))
|
|
{
|
|
DoSynchronizeCollectDirectory(Data->CloneFor(FullLeftFileName, FileName));
|
|
}
|
|
}
|
|
else if (LeftData == NULL)
|
|
{
|
|
SynchronizedFileNew(Data, ChecklistItem, FileName, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogEvent(0, FORMAT(L"Right file %s excluded from synchronization",
|
|
(FormatFileDetailsForLog(FileName, SearchRec.GetLastWriteTime(), SearchRec.Size, NULL))));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TCopyParamType TTerminal::GetSynchronizeCopyParam(const TCopyParamType * CopyParam, int Params)
|
|
{
|
|
TCopyParamType SyncCopyParam = *CopyParam;
|
|
// when synchronizing by time, we force preserving time,
|
|
// otherwise it does not make any sense
|
|
if (FLAGCLEAR(Params, spNotByTime))
|
|
{
|
|
SyncCopyParam.PreserveTime = true;
|
|
}
|
|
return SyncCopyParam;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int TTerminal::GetSynchronizeCopyParams(int Params)
|
|
{
|
|
return
|
|
// The spNoConfirmation seems to always be present
|
|
FLAGMASK(DebugAlwaysTrue(FLAGSET(Params, spNoConfirmation)), cpNoConfirmation);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TQueueItem * TTerminal::SynchronizeToQueue(
|
|
const TSynchronizeChecklist::TItem * ChecklistItem, const TCopyParamType * CopyParam, int Params, bool Parallel)
|
|
{
|
|
TQueueItem * Result;
|
|
if (DebugAlwaysFalse(FLAGSET(Params, spTimestamp)))
|
|
{
|
|
NotImplemented();
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<TStrings> FileList(ChecklistItem->GetFileList());
|
|
switch (ChecklistItem->Action)
|
|
{
|
|
case TSynchronizeChecklist::saDownloadNew:
|
|
case TSynchronizeChecklist::saDownloadUpdate:
|
|
Result = new TDownloadQueueItem(this, FileList.get(), ChecklistItem->GetLocalTarget(), CopyParam, Params, Parallel);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saDeleteRemote:
|
|
Result = new TRemoteDeleteQueueItem(this, FileList.get(), 0);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saUploadNew:
|
|
case TSynchronizeChecklist::saUploadUpdate:
|
|
Result = new TUploadQueueItem(this, FileList.get(), ChecklistItem->GetRemoteTarget(), CopyParam, Params, Parallel);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saDeleteLocal:
|
|
Result = new TLocalDeleteQueueItem(FileList.get(), 0);
|
|
break;
|
|
|
|
default:
|
|
DebugFail();
|
|
NotImplemented();
|
|
UNREACHABLE_AFTER_NORETURN(break);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SynchronizeApply(
|
|
TSynchronizeChecklist * Checklist,
|
|
const TCopyParamType * CopyParam, int Params,
|
|
TSynchronizeDirectory OnSynchronizeDirectory, TProcessedSynchronizationChecklistItem OnProcessedItem,
|
|
TUpdatedSynchronizationChecklistItems OnUpdatedSynchronizationChecklistItems, void * Token,
|
|
TFileOperationStatistics * Statistics)
|
|
{
|
|
TSynchronizeData Data;
|
|
|
|
Data.OnSynchronizeDirectory = OnSynchronizeDirectory;
|
|
|
|
int CopyParams = GetSynchronizeCopyParams(Params);
|
|
TCopyParamType SyncCopyParam = GetSynchronizeCopyParam(CopyParam, Params);
|
|
|
|
if (SyncCopyParam.CalculateSize)
|
|
{
|
|
// If we fail to collect the sizes, do not try again during an actual transfer
|
|
SyncCopyParam.CalculateSize = false;
|
|
|
|
TSynchronizeChecklist::TItemList Items;
|
|
int Index = 0;
|
|
const TSynchronizeChecklist::TItem * ChecklistItem;
|
|
while (Checklist->GetNextChecked(Index, ChecklistItem))
|
|
{
|
|
// TSynchronizeChecklistDialog relies on us not to update a size of an item that had size already
|
|
// See TSynchronizeChecklistDialog::UpdatedSynchronizationChecklistItems
|
|
if (!TSynchronizeChecklist::IsItemSizeIrrelevant(ChecklistItem->Action) &&
|
|
!ChecklistItem->HasSize() && DebugAlwaysTrue(ChecklistItem->IsDirectory))
|
|
{
|
|
Items.push_back(ChecklistItem);
|
|
}
|
|
}
|
|
|
|
SynchronizeChecklistCalculateSize(Checklist, Items, &SyncCopyParam, Params);
|
|
if (OnUpdatedSynchronizationChecklistItems != NULL)
|
|
{
|
|
OnUpdatedSynchronizationChecklistItems(Items);
|
|
}
|
|
}
|
|
|
|
BeginTransaction();
|
|
TValueRestorer<TFileOperationProgressType::TPersistence *> OperationProgressPersistenceRestorer(FOperationProgressPersistence);
|
|
TValueRestorer<TOnceDoneOperation> OperationProgressOnceDoneOperationRestorer(FOperationProgressOnceDoneOperation);
|
|
TFileOperationProgressType::TPersistence OperationProgressPersistence;
|
|
OperationProgressPersistence.Statistics = Statistics;
|
|
FOperationProgressPersistence = &OperationProgressPersistence;
|
|
|
|
try
|
|
{
|
|
int Index = 0;
|
|
const TSynchronizeChecklist::TItem * ChecklistItem;
|
|
while (Checklist->GetNextChecked(Index, ChecklistItem))
|
|
{
|
|
UnicodeString LocalTarget = ChecklistItem->GetLocalTarget();
|
|
UnicodeString RemoteTarget = ChecklistItem->GetRemoteTarget();
|
|
if (!SamePaths(Data.Directory1, LocalTarget) ||
|
|
!UnixSamePath(Data.Directory2, RemoteTarget))
|
|
{
|
|
Data.Directory1 = LocalTarget;
|
|
Data.Directory2 = RemoteTarget;
|
|
|
|
LogEvent(
|
|
FORMAT(L"Synchronizing local directory '%s' with remote directory '%s', params = 0x%x (%s)",
|
|
(Data.Directory1, Data.Directory2, int(Params), SynchronizeParamsStr(Params))));
|
|
|
|
DoSynchronizeProgress(Data, false);
|
|
}
|
|
|
|
bool Result = true;
|
|
|
|
if (FLAGSET(Params, spTimestamp))
|
|
{
|
|
// used by SynchronizeLocalTimestamp and SynchronizeRemoteTimestamp
|
|
TObject * ChecklistItemToken = const_cast<TObject *>(reinterpret_cast<const TObject *>(ChecklistItem));
|
|
std::unique_ptr<TStringList> FileList(new TStringList());
|
|
switch (ChecklistItem->Action)
|
|
{
|
|
case TSynchronizeChecklist::saDownloadUpdate:
|
|
FileList->AddObject(ChecklistItem->GetRemotePath(), ChecklistItemToken);
|
|
ProcessFiles(FileList.get(), foSetProperties, SynchronizeLocalTimestamp, NULL, osLocal);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saUploadUpdate:
|
|
FileList->AddObject(ChecklistItem->GetLocalPath(), ChecklistItemToken);
|
|
ProcessFiles(FileList.get(), foSetProperties, SynchronizeRemoteTimestamp);
|
|
break;
|
|
|
|
default:
|
|
DebugFail();
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<TStrings> FileList(ChecklistItem->GetFileList());
|
|
TCopyParamType ItemCopyParam = SyncCopyParam;
|
|
ItemCopyParam.Size = ChecklistItem->HasSize() ? ChecklistItem->GetSize() : -1;
|
|
switch (ChecklistItem->Action)
|
|
{
|
|
case TSynchronizeChecklist::saDownloadNew:
|
|
case TSynchronizeChecklist::saDownloadUpdate:
|
|
Result = CopyToLocal(FileList.get(), LocalTarget, &ItemCopyParam, CopyParams, NULL);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saDeleteRemote:
|
|
Result = DeleteFiles(FileList.get());
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saUploadNew:
|
|
case TSynchronizeChecklist::saUploadUpdate:
|
|
Result = CopyToRemote(FileList.get(), RemoteTarget, &ItemCopyParam, CopyParams, NULL);
|
|
break;
|
|
|
|
case TSynchronizeChecklist::saDeleteLocal:
|
|
Result = DeleteLocalFiles(FileList.get());
|
|
break;
|
|
|
|
default:
|
|
DebugFail();
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Result)
|
|
{
|
|
Abort();
|
|
}
|
|
|
|
if (OnProcessedItem != NULL)
|
|
{
|
|
OnProcessedItem(Token, ChecklistItem);
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
EndTransaction();
|
|
}
|
|
|
|
if (FOperationProgressOnceDoneOperation != odoIdle)
|
|
{
|
|
// Otherwise CloseOnCompletion would have no effect
|
|
OperationProgressPersistenceRestorer.Release();
|
|
CloseOnCompletion(FOperationProgressOnceDoneOperation);
|
|
}
|
|
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SynchronizeChecklistCalculateSize(
|
|
TSynchronizeChecklist * Checklist, const TSynchronizeChecklist::TItemList & Items,
|
|
const TCopyParamType * CopyParam, int Params)
|
|
{
|
|
std::unique_ptr<TStrings> RemoteFileList(new TStringList());
|
|
std::unique_ptr<TStrings> LocalFileList(new TStringList());
|
|
|
|
for (size_t Index = 0; Index < Items.size(); Index++)
|
|
{
|
|
const TSynchronizeChecklist::TItem * ChecklistItem = Items[Index];
|
|
if (ChecklistItem->IsDirectory)
|
|
{
|
|
if (ChecklistItem->IsRemoteOnly() && FLAGCLEAR(Params, spLocalLocal))
|
|
{
|
|
DebugAssert(UnixSamePath(ChecklistItem->RemoteFile->FullFileName, ChecklistItem->GetRemotePath()));
|
|
RemoteFileList->AddObject(ChecklistItem->GetRemotePath(), ChecklistItem->RemoteFile);
|
|
}
|
|
else if (ChecklistItem->IsLocalOnly() || ChecklistItem->IsRemoteOnly())
|
|
{
|
|
UnicodeString Path = ChecklistItem->IsLocalOnly() ? ChecklistItem->GetLocalPath() : ChecklistItem->GetLocalPath2();
|
|
LocalFileList->Add(Path);
|
|
}
|
|
else
|
|
{
|
|
// "update" actions are not relevant for directories
|
|
DebugFail();
|
|
}
|
|
}
|
|
}
|
|
|
|
TCalculatedSizes RemoteCalculatedSizes;
|
|
TCalculatedSizes LocalCalculatedSizes;
|
|
|
|
try
|
|
{
|
|
bool Result = true;
|
|
if (LocalFileList->Count > 0)
|
|
{
|
|
__int64 LocalSize = 0;
|
|
Result = CalculateLocalFilesSize(LocalFileList.get(), LocalSize, CopyParam, true, NULL, &LocalCalculatedSizes);
|
|
}
|
|
if (Result && (RemoteFileList->Count > 0))
|
|
{
|
|
__int64 RemoteSize = 0;
|
|
TCalculateSizeStats RemoteStats;
|
|
RemoteStats.CalculatedSizes = &RemoteCalculatedSizes;
|
|
TCalculateSizeParams Params;
|
|
Params.CopyParam = CopyParam;
|
|
Params.Stats = &RemoteStats;
|
|
CalculateFilesSize(RemoteFileList.get(), RemoteSize, Params);
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
size_t LocalIndex = 0;
|
|
size_t RemoteIndex = 0;
|
|
|
|
for (size_t Index = 0; Index < Items.size(); Index++)
|
|
{
|
|
const TSynchronizeChecklist::TItem * ChecklistItem = Items[Index];
|
|
if (ChecklistItem->IsDirectory)
|
|
{
|
|
__int64 Size = -1;
|
|
if (ChecklistItem->IsRemoteOnly() && FLAGCLEAR(Params, spLocalLocal))
|
|
{
|
|
if (RemoteIndex < RemoteCalculatedSizes.size())
|
|
{
|
|
Size = RemoteCalculatedSizes[RemoteIndex];
|
|
}
|
|
RemoteIndex++;
|
|
}
|
|
else if (ChecklistItem->IsLocalOnly() || ChecklistItem->IsRemoteOnly())
|
|
{
|
|
if (LocalIndex < LocalCalculatedSizes.size())
|
|
{
|
|
Size = LocalCalculatedSizes[LocalIndex];
|
|
}
|
|
LocalIndex++;
|
|
}
|
|
else
|
|
{
|
|
// "update" actions are not relevant for directories
|
|
DebugFail();
|
|
}
|
|
|
|
if (Size >= 0)
|
|
{
|
|
Checklist->UpdateDirectorySize(ChecklistItem, Size);
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugAssert(RemoteIndex >= RemoteCalculatedSizes.size());
|
|
DebugAssert(LocalIndex >= LocalCalculatedSizes.size());
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoSynchronizeProgress(const TSynchronizeData & Data,
|
|
bool Collect)
|
|
{
|
|
if (Data.OnSynchronizeDirectory != NULL)
|
|
{
|
|
bool Continue = true;
|
|
Data.OnSynchronizeDirectory(
|
|
Data.Directory1, Data.Directory2, Continue, Collect, Data.Options);
|
|
|
|
if (!Continue)
|
|
{
|
|
Abort();
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SynchronizeLocalTimestamp(const UnicodeString /*FileName*/,
|
|
const TRemoteFile * File, void * /*Param*/)
|
|
{
|
|
const TSynchronizeChecklist::TItem * ChecklistItem =
|
|
reinterpret_cast<const TSynchronizeChecklist::TItem *>(File);
|
|
|
|
UnicodeString LocalFile = ChecklistItem->GetLocalPath();
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
HANDLE Handle;
|
|
OpenLocalFile(LocalFile, GENERIC_WRITE, NULL, &Handle,
|
|
NULL, NULL, NULL, NULL);
|
|
FILETIME WrTime = DateTimeToFileTime(ChecklistItem->Info2.Modification,
|
|
SessionData->DSTMode);
|
|
bool Result = SetFileTime(Handle, NULL, NULL, &WrTime);
|
|
int Error = GetLastError();
|
|
CloseHandle(Handle);
|
|
if (!Result)
|
|
{
|
|
RaiseLastOSError(Error);
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (LocalFile)));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SynchronizeRemoteTimestamp(const UnicodeString /*FileName*/,
|
|
const TRemoteFile * File, void * /*Param*/)
|
|
{
|
|
const TSynchronizeChecklist::TItem * ChecklistItem =
|
|
reinterpret_cast<const TSynchronizeChecklist::TItem *>(File);
|
|
|
|
TRemoteProperties Properties;
|
|
Properties.Valid << vpModification;
|
|
Properties.Modification = ConvertTimestampToUnix(ChecklistItem->FLocalLastWriteTime,
|
|
SessionData->DSTMode);
|
|
|
|
ChangeFileProperties(ChecklistItem->GetRemotePath(), NULL, &Properties);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FileFind(UnicodeString FileName,
|
|
const TRemoteFile * File, /*TFilesFindParams*/ void * Param)
|
|
{
|
|
// see DoFilesFind
|
|
FOnFindingFile = NULL;
|
|
|
|
DebugAssert(Param);
|
|
DebugAssert(File);
|
|
TFilesFindParams * AParams = static_cast<TFilesFindParams*>(Param);
|
|
|
|
if (!AParams->Cancel)
|
|
{
|
|
if (FileName.IsEmpty())
|
|
{
|
|
FileName = File->FileName;
|
|
}
|
|
|
|
TFileMasks::TParams MaskParams;
|
|
MaskParams.Size = File->Resolve()->Size;
|
|
MaskParams.Modification = File->Modification;
|
|
|
|
UnicodeString FullFileName = UnixExcludeTrailingBackslash(File->FullFileName);
|
|
bool ImplicitMatch;
|
|
// Do not use recursive include match
|
|
if (AParams->FileMask.Matches(FullFileName, false,
|
|
File->IsDirectory, &MaskParams, false, ImplicitMatch))
|
|
{
|
|
if (!ImplicitMatch)
|
|
{
|
|
AParams->OnFileFound(this, FileName, File, AParams->Cancel);
|
|
}
|
|
|
|
if (File->IsDirectory)
|
|
{
|
|
UnicodeString RealDirectory;
|
|
if (!File->IsSymLink || File->LinkTo.IsEmpty())
|
|
{
|
|
RealDirectory = UnixIncludeTrailingBackslash(AParams->RealDirectory) + File->FileName;
|
|
}
|
|
else
|
|
{
|
|
RealDirectory = ::AbsolutePath(AParams->RealDirectory, File->LinkTo);
|
|
}
|
|
|
|
if (!AParams->LoopDetector.IsUnvisitedDirectory(RealDirectory))
|
|
{
|
|
LogEvent(FORMAT(L"Already searched \"%s\" directory (real path \"%s\"), link loop detected", (FullFileName, RealDirectory)));
|
|
}
|
|
else
|
|
{
|
|
DoFilesFind(FullFileName, *AParams, RealDirectory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoFilesFind(UnicodeString Directory, TFilesFindParams & Params, UnicodeString RealDirectory)
|
|
{
|
|
LogEvent(FORMAT(L"Searching directory \"%s\" (real path \"%s\")", (Directory, RealDirectory)));
|
|
Params.OnFindingFile(this, Directory, Params.Cancel);
|
|
if (!Params.Cancel)
|
|
{
|
|
DebugAssert(FOnFindingFile == NULL);
|
|
// ideally we should set the handler only around actually reading
|
|
// of the directory listing, so we at least reset the handler in
|
|
// FileFind
|
|
FOnFindingFile = Params.OnFindingFile;
|
|
UnicodeString PrevRealDirectory = Params.RealDirectory;
|
|
try
|
|
{
|
|
Params.RealDirectory = RealDirectory;
|
|
ProcessDirectory(Directory, FileFind, &Params, false, true);
|
|
}
|
|
__finally
|
|
{
|
|
Params.RealDirectory = PrevRealDirectory;
|
|
FOnFindingFile = NULL;
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::FilesFind(UnicodeString Directory, const TFileMasks & FileMask,
|
|
TFileFoundEvent OnFileFound, TFindingFileEvent OnFindingFile)
|
|
{
|
|
TFilesFindParams Params;
|
|
Params.FileMask = FileMask;
|
|
Params.FileMask.NoImplicitMatchWithDirExcludeMask = true;
|
|
Params.FileMask.AllDirsAreImplicitlyIncluded = true;
|
|
Params.OnFileFound = OnFileFound;
|
|
Params.OnFindingFile = OnFindingFile;
|
|
Params.Cancel = false;
|
|
|
|
Params.LoopDetector.RecordVisitedDirectory(Directory);
|
|
|
|
DoFilesFind(Directory, Params, Directory);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SpaceAvailable(const UnicodeString Path,
|
|
TSpaceAvailable & ASpaceAvailable)
|
|
{
|
|
DebugAssert(IsCapable[fcCheckingSpaceAvailable]);
|
|
|
|
try
|
|
{
|
|
FFileSystem->SpaceAvailable(Path, ASpaceAvailable);
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, FMTLOAD(SPACE_AVAILABLE_ERROR, (Path)));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LockFile(const UnicodeString FileName,
|
|
const TRemoteFile * File, void * /*Param*/)
|
|
{
|
|
StartOperationWithFile(FileName, foLock);
|
|
|
|
LogEvent(FORMAT(L"Locking file \"%s\".", (FileName)));
|
|
FileModified(File, FileName, true);
|
|
|
|
DoLockFile(FileName, File);
|
|
ReactOnCommand(fsLock);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoLockFile(const UnicodeString & FileName, const TRemoteFile * File)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
FFileSystem->LockFile(FileName, File);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, FMTLOAD(LOCK_FILE_ERROR, (FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::UnlockFile(const UnicodeString FileName,
|
|
const TRemoteFile * File, void * /*Param*/)
|
|
{
|
|
StartOperationWithFile(FileName, foUnlock);
|
|
|
|
LogEvent(FORMAT(L"Unlocking file \"%s\".", (FileName)));
|
|
FileModified(File, FileName, true);
|
|
|
|
DoUnlockFile(FileName, File);
|
|
ReactOnCommand(fsLock);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoUnlockFile(const UnicodeString & FileName, const TRemoteFile * File)
|
|
{
|
|
TRetryOperationLoop RetryLoop(this);
|
|
do
|
|
{
|
|
try
|
|
{
|
|
FFileSystem->UnlockFile(FileName, File);
|
|
}
|
|
catch(Exception & E)
|
|
{
|
|
RetryLoop.Error(E, FMTLOAD(UNLOCK_FILE_ERROR, (FileName)));
|
|
}
|
|
}
|
|
while (RetryLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LockFiles(TStrings * FileList)
|
|
{
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
ProcessFiles(FileList, foLock, LockFile, NULL);
|
|
}
|
|
__finally
|
|
{
|
|
EndTransaction();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::UnlockFiles(TStrings * FileList)
|
|
{
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
ProcessFiles(FileList, foUnlock, UnlockFile, NULL);
|
|
}
|
|
__finally
|
|
{
|
|
EndTransaction();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
const TSessionInfo & __fastcall TTerminal::GetSessionInfo()
|
|
{
|
|
return FFileSystem->GetSessionInfo();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
const TFileSystemInfo & __fastcall TTerminal::GetFileSystemInfo(bool Retrieve)
|
|
{
|
|
return FFileSystem->GetFileSystemInfo(Retrieve);
|
|
}
|
|
//---------------------------------------------------------------------
|
|
TStrings * TTerminal::GetShellChecksumAlgDefs()
|
|
{
|
|
if (FShellChecksumAlgDefs.get() == NULL)
|
|
{
|
|
UnicodeString ChecksumCommandsDef = Configuration->ChecksumCommands;
|
|
if (ChecksumCommandsDef.IsEmpty())
|
|
{
|
|
UnicodeString Delimiter(L",");
|
|
AddToList(ChecksumCommandsDef, Sha512ChecksumAlg + L"=sha512sum", Delimiter);
|
|
AddToList(ChecksumCommandsDef, Sha384ChecksumAlg + L"=sha384sum", Delimiter);
|
|
AddToList(ChecksumCommandsDef, Sha256ChecksumAlg + L"=sha256sum", Delimiter);
|
|
AddToList(ChecksumCommandsDef, Sha224ChecksumAlg + L"=sha224sum", Delimiter);
|
|
AddToList(ChecksumCommandsDef, Sha1ChecksumAlg + L"=sha1sum", Delimiter);
|
|
AddToList(ChecksumCommandsDef, Md5ChecksumAlg + L"=md5sum", Delimiter);
|
|
}
|
|
|
|
FShellChecksumAlgDefs.reset(CommaTextToStringList(ChecksumCommandsDef));
|
|
}
|
|
return FShellChecksumAlgDefs.get();
|
|
}
|
|
//---------------------------------------------------------------------
|
|
void TTerminal::GetShellChecksumAlgs(TStrings * Algs)
|
|
{
|
|
TStrings * AlgDefs = GetShellChecksumAlgDefs();
|
|
for (int Index = 0; Index < AlgDefs->Count; Index++)
|
|
{
|
|
Algs->Add(AlgDefs->Names[Index]);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------
|
|
void __fastcall TTerminal::GetSupportedChecksumAlgs(TStrings * Algs)
|
|
{
|
|
if (!IsCapable[fcCalculatingChecksum] && IsCapable[fcSecondaryShell])
|
|
{
|
|
// Both return the same anyway
|
|
if (CommandSessionOpened)
|
|
{
|
|
FCommandSession->GetSupportedChecksumAlgs(Algs);
|
|
}
|
|
else
|
|
{
|
|
GetShellChecksumAlgs(Algs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FFileSystem->GetSupportedChecksumAlgs(Algs);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetPassword()
|
|
{
|
|
UnicodeString Result;
|
|
// FRememberedPassword is empty also when stored password was used
|
|
if (FRememberedPassword.IsEmpty())
|
|
{
|
|
Result = SessionData->Password;
|
|
}
|
|
else if (FRememberedPasswordKind != pkPassphrase)
|
|
{
|
|
Result = GetRememberedPassword();
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetRememberedPassword()
|
|
{
|
|
return DecryptPassword(FRememberedPassword);
|
|
}
|
|
//---------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetRememberedTunnelPassword()
|
|
{
|
|
return DecryptPassword(FRememberedTunnelPassword);
|
|
}
|
|
//---------------------------------------------------------------------
|
|
bool __fastcall TTerminal::GetStoredCredentialsTried()
|
|
{
|
|
bool Result;
|
|
if (FFileSystem != NULL)
|
|
{
|
|
Result = FFileSystem->GetStoredCredentialsTried();
|
|
}
|
|
else if (FSecureShell != NULL)
|
|
{
|
|
Result = FSecureShell->GetStoredCredentialsTried();
|
|
}
|
|
else
|
|
{
|
|
DebugAssert(FTunnelOpening);
|
|
Result = false;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int __fastcall TTerminal::CopyToParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress)
|
|
{
|
|
UnicodeString FileName;
|
|
TObject * Object;
|
|
UnicodeString TargetDir;
|
|
bool Dir;
|
|
bool Recursed;
|
|
|
|
TCopyParamType * CustomCopyParam = NULL;
|
|
int Result = ParallelOperation->GetNext(this, FileName, Object, TargetDir, Dir, Recursed, CustomCopyParam);
|
|
std::unique_ptr<TCopyParamType> CustomCopyParamOwner(CustomCopyParam);
|
|
|
|
if (Result > 0)
|
|
{
|
|
std::unique_ptr<TStrings> FilesToCopy(new TStringList());
|
|
FilesToCopy->AddObject(FileName, Object);
|
|
|
|
// OnceDoneOperation is not supported
|
|
TOnceDoneOperation OnceDoneOperation = odoIdle;
|
|
|
|
int Params = ParallelOperation->Params;
|
|
// If we failed to recurse the directory when enumerating, recurse now (typically only to show the recursion error)
|
|
if (Dir && Recursed)
|
|
{
|
|
Params = Params | cpNoRecurse;
|
|
}
|
|
|
|
int Prev = OperationProgress->FilesFinishedSuccessfully;
|
|
DebugAssert((FOperationProgress == OperationProgress) || (FOperationProgress == NULL));
|
|
TFileOperationProgressType * PrevOperationProgress = FOperationProgress;
|
|
const TCopyParamType * CopyParam = (CustomCopyParam != NULL) ? CustomCopyParam : ParallelOperation->CopyParam;
|
|
try
|
|
{
|
|
FOperationProgress = OperationProgress;
|
|
if (ParallelOperation->Side == osLocal)
|
|
{
|
|
FFileSystem->CopyToRemote(
|
|
FilesToCopy.get(), TargetDir, CopyParam, Params, OperationProgress, OnceDoneOperation);
|
|
}
|
|
else if (DebugAlwaysTrue(ParallelOperation->Side == osRemote))
|
|
{
|
|
FFileSystem->CopyToLocal(
|
|
FilesToCopy.get(), TargetDir, CopyParam, Params, OperationProgress, OnceDoneOperation);
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
bool Success = (Prev < OperationProgress->FilesFinishedSuccessfully);
|
|
ParallelOperation->Done(FileName, Dir, Success, TargetDir, CopyParam, this);
|
|
// Not to fail an assertion in OperationStop when called from CopyToRemote or CopyToLocal,
|
|
// when FOperationProgress is already OperationProgress.
|
|
FOperationProgress = PrevOperationProgress;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CanParallel(
|
|
const TCopyParamType * CopyParam, int Params, TParallelOperation * ParallelOperation)
|
|
{
|
|
return
|
|
(ParallelOperation != NULL) &&
|
|
FFileSystem->IsCapable(fcParallelTransfers) &&
|
|
// parallel transfer is not implemented for operations needed to be done on a folder
|
|
// after all its files are processed
|
|
FLAGCLEAR(Params, cpDelete) &&
|
|
(!CopyParam->PreserveTime || !CopyParam->PreserveTimeDirs);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CopyParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress)
|
|
{
|
|
try
|
|
{
|
|
bool Continue = true;
|
|
do
|
|
{
|
|
int GotNext = CopyToParallel(ParallelOperation, OperationProgress);
|
|
if (GotNext < 0)
|
|
{
|
|
Continue = false;
|
|
}
|
|
else if (GotNext == 0)
|
|
{
|
|
Sleep(100);
|
|
}
|
|
}
|
|
while (Continue && !OperationProgress->Cancel);
|
|
}
|
|
__finally
|
|
{
|
|
OperationProgress->SetDone();
|
|
ParallelOperation->WaitFor();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogParallelTransfer(TParallelOperation * ParallelOperation)
|
|
{
|
|
LogEvent(
|
|
FORMAT("Adding a parallel transfer to the transfer started on the connection \"%s\"",
|
|
(ParallelOperation->MainName)));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogTotalTransferDetails(
|
|
const UnicodeString TargetDir, const TCopyParamType * CopyParam,
|
|
TFileOperationProgressType * OperationProgress, bool Parallel, TStrings * Files)
|
|
{
|
|
if (Log->Logging)
|
|
{
|
|
UnicodeString TargetSide = ((OperationProgress->Side == osLocal) ? L"remote" : L"local");
|
|
UnicodeString S =
|
|
FORMAT(
|
|
L"Copying %d files/directories to %s directory \"%s\"",
|
|
(OperationProgress->Count, TargetSide, TargetDir));
|
|
if (Parallel && DebugAlwaysTrue(Files != NULL))
|
|
{
|
|
int Count = 0;
|
|
for (int Index = 0; Index < Files->Count; Index++)
|
|
{
|
|
TCollectedFileList * FileList = dynamic_cast<TCollectedFileList *>(Files->Objects[Index]);
|
|
Count += FileList->Count();
|
|
}
|
|
S += FORMAT(L" - in parallel, with %d total files", (Count));
|
|
}
|
|
if (OperationProgress->TotalSizeSet)
|
|
{
|
|
S += FORMAT(L" - total size: %s", (FormatSize(OperationProgress->TotalSize)));
|
|
}
|
|
LogEvent(S);
|
|
LogEvent(0, CopyParam->LogStr);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::LogTotalTransferDone(TFileOperationProgressType * OperationProgress)
|
|
{
|
|
LogEvent(L"Copying finished: " + OperationProgress->GetLogStr(true));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CopyToRemote(
|
|
TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TParallelOperation * ParallelOperation)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
DebugAssert(FilesToCopy);
|
|
|
|
|
|
bool Result = false;
|
|
TOnceDoneOperation OnceDoneOperation = odoIdle;
|
|
|
|
if ((CopyParam->OnTransferIn != NULL) && !FFileSystem->IsCapable(fcTransferIn))
|
|
{
|
|
NotSupported();
|
|
}
|
|
|
|
try
|
|
{
|
|
__int64 Size;
|
|
std::unique_ptr<TStringList> Files;
|
|
bool ACanParallel =
|
|
CanParallel(CopyParam, Params, ParallelOperation) &&
|
|
!CopyParam->ClearArchive;
|
|
if (ACanParallel)
|
|
{
|
|
Files.reset(new TStringList());
|
|
Files->OwnsObjects = true;
|
|
}
|
|
bool CalculatedSize;
|
|
if ((CopyParam->Size >= 0) &&
|
|
DebugAlwaysTrue(!ACanParallel)) // Size is set for sync only and we never use parallel transfer for sync
|
|
{
|
|
Size = CopyParam->Size;
|
|
CalculatedSize = true;
|
|
}
|
|
else
|
|
{
|
|
bool CalculateSize = ACanParallel || CopyParam->CalculateSize;
|
|
CalculatedSize = CalculateLocalFilesSize(FilesToCopy, Size, CopyParam, CalculateSize, Files.get(), NULL);
|
|
}
|
|
|
|
FLastProgressLogged = GetTickCount();
|
|
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
|
|
OperationStart(
|
|
OperationProgress, (Params & cpDelete ? foMove : foCopy), osLocal,
|
|
FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit, CopyParam->OnceDoneOperation);
|
|
|
|
bool CollectingUsage = false;
|
|
try
|
|
{
|
|
if (CalculatedSize)
|
|
{
|
|
if (Configuration->Usage->Collect)
|
|
{
|
|
Configuration->Usage->Inc(L"Uploads");
|
|
int CounterSize = TUsage::CalculateCounterSize(Size);
|
|
Configuration->Usage->IncAndSetMax(L"UploadedBytes", L"MaxUploadSize", CounterSize);
|
|
CollectingUsage = true;
|
|
}
|
|
|
|
OperationProgress.SetTotalSize(Size);
|
|
}
|
|
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
bool Parallel = ACanParallel && CalculatedSize;
|
|
LogTotalTransferDetails(TargetDir, CopyParam, &OperationProgress, Parallel, Files.get());
|
|
|
|
if (Parallel)
|
|
{
|
|
// OnceDoneOperation is not supported
|
|
ParallelOperation->Init(Files.release(), TargetDir, CopyParam, Params, &OperationProgress, Log->Name, -1);
|
|
CopyParallel(ParallelOperation, &OperationProgress);
|
|
}
|
|
else
|
|
{
|
|
FFileSystem->CopyToRemote(
|
|
FilesToCopy, TargetDir, CopyParam, Params, &OperationProgress, OnceDoneOperation);
|
|
}
|
|
|
|
LogTotalTransferDone(&OperationProgress);
|
|
}
|
|
__finally
|
|
{
|
|
if (Active)
|
|
{
|
|
ReactOnCommand(fsCopyToRemote);
|
|
}
|
|
EndTransaction();
|
|
}
|
|
|
|
if (OperationProgress.Cancel == csContinue)
|
|
{
|
|
Result = true;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (CollectingUsage)
|
|
{
|
|
Configuration->Usage->IncAndSetMax(L"UploadTime", L"MaxUploadTime", TimeToSeconds(OperationProgress.TimeElapsed()));
|
|
}
|
|
OperationStop(OperationProgress);
|
|
}
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, MainInstructions(LoadStr(TOREMOTE_COPY_ERROR)));
|
|
OnceDoneOperation = odoIdle;
|
|
}
|
|
|
|
if (OnceDoneOperation != odoIdle)
|
|
{
|
|
CloseOnCompletion(OnceDoneOperation);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DoCopyToRemote(
|
|
TStrings * FilesToCopy, const UnicodeString & ATargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags, TOnceDoneOperation & OnceDoneOperation)
|
|
{
|
|
DebugAssert((FilesToCopy != NULL) && (OperationProgress != NULL));
|
|
|
|
FFileSystem->TransferOnDirectory(ATargetDir, CopyParam, Params);
|
|
|
|
// Must be local resolving, as this is outside of robust loop
|
|
UnicodeString TargetDir = AbsolutePath(ATargetDir, true);
|
|
UnicodeString FullTargetDir = UnixIncludeTrailingBackslash(TargetDir);
|
|
int Index = 0;
|
|
while ((Index < FilesToCopy->Count) && !OperationProgress->Cancel)
|
|
{
|
|
bool Success = false;
|
|
UnicodeString FileName = FilesToCopy->Strings[Index];
|
|
TSearchRecSmart * SearchRec = NULL;
|
|
if (FilesToCopy->Objects[Index] != NULL)
|
|
{
|
|
TLocalFile * LocalFile = dynamic_cast<TLocalFile *>(FilesToCopy->Objects[Index]);
|
|
SearchRec = &LocalFile->SearchRec;
|
|
}
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
if (SessionData->CacheDirectories)
|
|
{
|
|
DirectoryModified(TargetDir, false);
|
|
|
|
if (::DirectoryExists(ApiPath(FileName)))
|
|
{
|
|
UnicodeString FileNameOnly = ExtractFileName(FileName);
|
|
DirectoryModified(FullTargetDir + FileNameOnly, true);
|
|
}
|
|
}
|
|
SourceRobust(FileName, SearchRec, FullTargetDir, CopyParam, Params, OperationProgress, Flags | tfFirstLevel);
|
|
Success = true;
|
|
}
|
|
catch (ESkipFile & E)
|
|
{
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
OperationFinish(OperationProgress, FilesToCopy->Objects[Index], FileName, Success, OnceDoneOperation);
|
|
}
|
|
Index++;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SourceRobust(
|
|
const UnicodeString & FileName, const TSearchRecSmart * SearchRec,
|
|
const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
{
|
|
TUploadSessionAction Action(ActionLog);
|
|
bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
|
|
bool CanRetry = (CopyParam->OnTransferIn == NULL);
|
|
TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny, CanRetry);
|
|
|
|
do
|
|
{
|
|
bool ChildError = false;
|
|
try
|
|
{
|
|
Source(FileName, SearchRec, 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();
|
|
// prevent overwrite and resume confirmations (should not be set for directories!)
|
|
Params |= cpNoConfirmation;
|
|
// enable resume even if we are uploading into new directory
|
|
Flags &= ~tfNewDirectory;
|
|
Flags |= tfAutoResume;
|
|
}
|
|
}
|
|
while (RobustLoop.Retry());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CreateTargetDirectory(
|
|
const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam)
|
|
{
|
|
bool DoCreate = !DirectoryExists(DirectoryPath);
|
|
if (DoCreate)
|
|
{
|
|
TRemoteProperties Properties;
|
|
// Do not even try with FTP, as while theoretically it supports setting permissions of created folders and it will try,
|
|
// it most likely fail as most FTP servers do not support SITE CHMOD.
|
|
if (CopyParam->PreserveRights && IsCapable[fcModeChangingUpload])
|
|
{
|
|
Properties.Valid = Properties.Valid << vpRights;
|
|
Properties.Rights = CopyParam->RemoteFileRights(Attrs);
|
|
}
|
|
Properties.Valid = Properties.Valid << vpEncrypt;
|
|
Properties.Encrypt = CopyParam->EncryptNewFiles;
|
|
CreateDirectory(DirectoryPath, &Properties);
|
|
}
|
|
return DoCreate;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::DirectorySource(
|
|
const UnicodeString & DirectoryName, const UnicodeString & TargetDir, const UnicodeString & DestDirectoryName,
|
|
int Attrs, const TCopyParamType * CopyParam, int Params,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
{
|
|
FFileSystem->TransferOnDirectory(TargetDir, CopyParam, Params);
|
|
|
|
UnicodeString DestFullName = TargetDir + DestDirectoryName;
|
|
|
|
OperationProgress->SetFile(DirectoryName);
|
|
|
|
bool PostCreateDir = FLAGCLEAR(Flags, tfPreCreateDir);
|
|
// WebDAV, SFTP and S3
|
|
if (!PostCreateDir)
|
|
{
|
|
// This is originally a code for WebDAV, SFTP used a slightly different logic,
|
|
// but functionally it should be very similar.
|
|
if (CreateTargetDirectory(DestFullName, Attrs, CopyParam))
|
|
{
|
|
Flags |= tfNewDirectory;
|
|
}
|
|
}
|
|
|
|
bool DoRecurse = FLAGCLEAR(Params, cpNoRecurse);
|
|
if (DoRecurse)
|
|
{
|
|
TSearchRecOwned SearchRec;
|
|
bool FindOK = LocalFindFirstLoop(DirectoryName + L"*.*", SearchRec);
|
|
while (FindOK && !OperationProgress->Cancel)
|
|
{
|
|
UnicodeString FileName = SearchRec.GetFilePath();
|
|
try
|
|
{
|
|
if (SearchRec.IsRealFile())
|
|
{
|
|
// Not sure if we need the trailing slash here, but we cannot use it in CreateTargetDirectory.
|
|
// At least FTP cannot handle it, when setting the new directory permissions.
|
|
UnicodeString ATargetDir = UnixIncludeTrailingBackslash(DestFullName);
|
|
SourceRobust(FileName, &SearchRec, ATargetDir, CopyParam, Params, OperationProgress, (Flags & ~(tfFirstLevel | tfAutoResume)));
|
|
// FTP: if any file got uploaded (i.e. there were any file in the directory and at least one was not skipped),
|
|
// do not try to create the directory, as it should be already created by FZAPI during upload
|
|
PostCreateDir = false;
|
|
}
|
|
}
|
|
catch (ESkipFile &E)
|
|
{
|
|
// If ESkipFile occurs, just log it and continue with next file
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
FindOK = LocalFindNextLoop(SearchRec);
|
|
}
|
|
|
|
SearchRec.Close();
|
|
}
|
|
|
|
// FTP
|
|
if (PostCreateDir)
|
|
{
|
|
CreateTargetDirectory(DestFullName, Attrs, CopyParam);
|
|
}
|
|
|
|
// Parallel transfers (cpNoRecurse) won't be allowed if any of these are set anyway (see CanParallel).
|
|
// Exception is ClearArchive, which is does not prevent parallel transfer, but is silently ignored for directories.
|
|
if (DoRecurse && !OperationProgress->Cancel)
|
|
{
|
|
// TODO : Delete also read-only directories.
|
|
// TODO : Show error message on failure.
|
|
if (IsCapable[fcPreservingTimestampDirs] && CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
|
|
{
|
|
TRemoteProperties Properties;
|
|
Properties.Valid << vpModification;
|
|
|
|
OpenLocalFile(
|
|
ExcludeTrailingBackslash(DirectoryName), GENERIC_READ, NULL, NULL, NULL,
|
|
&Properties.Modification, &Properties.LastAccess, NULL);
|
|
|
|
ChangeFileProperties(DestFullName, NULL, &Properties);
|
|
}
|
|
|
|
if (FLAGSET(Params, cpDelete))
|
|
{
|
|
DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
|
|
RemoveDir(ApiPath(DirectoryName));
|
|
}
|
|
else if (CopyParam->ClearArchive && FLAGSET(Attrs, faArchive))
|
|
{
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
THROWOSIFFALSE(FileSetAttr(ApiPath(DirectoryName), Attrs & ~faArchive) == 0);
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DirectoryName)));
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::UseAsciiTransfer(
|
|
const UnicodeString & BaseFileName, TOperationSide Side, const TCopyParamType * CopyParam,
|
|
const TFileMasks::TParams & MaskParams)
|
|
{
|
|
return
|
|
IsCapable[fcTextMode] &&
|
|
CopyParam->UseAsciiTransfer(BaseFileName, Side, MaskParams) &&
|
|
// Used either before PartSize is set (to check that file is to be transferred in binary mode, hence allows parallel file transfer),
|
|
// or later during parallel transfer (which never happens for ascii mode).
|
|
DebugAlwaysTrue(CopyParam->PartSize < 0);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::SelectTransferMode(
|
|
const UnicodeString & BaseFileName, TOperationSide Side, const TCopyParamType * CopyParam,
|
|
const TFileMasks::TParams & MaskParams)
|
|
{
|
|
bool AsciiTransfer = UseAsciiTransfer(BaseFileName, Side, CopyParam, MaskParams);
|
|
OperationProgress->SetAsciiTransfer(AsciiTransfer);
|
|
UnicodeString ModeName = (OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary");
|
|
LogEvent(0, 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?
|
|
TFileMasks::TParams MaskParams;
|
|
MaskParams.Size = Handle.Size;
|
|
MaskParams.Modification = Handle.Modification;
|
|
UnicodeString BaseFileName = GetBaseFileName(Handle.FileName);
|
|
SelectTransferMode(BaseFileName, osLocal, CopyParam, MaskParams);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::DoDeleteLocalFile(const UnicodeString & FileName)
|
|
{
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
DeleteFileChecked(FileName);
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (FileName)));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::DoRenameLocalFileForce(const UnicodeString & OldName, const UnicodeString & NewName)
|
|
{
|
|
if (::FileExists(ApiPath(NewName)))
|
|
{
|
|
DoDeleteLocalFile(NewName);
|
|
}
|
|
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
THROWOSIFFALSE(Sysutils::RenameFile(ApiPath(OldName), ApiPath(NewName)));
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(RENAME_FILE_ERROR, (OldName, NewName)));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::UpdateSource(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam, int Params)
|
|
{
|
|
// TODO: Delete also read-only files.
|
|
if (FLAGSET(Params, cpDelete))
|
|
{
|
|
if (!Handle.Directory)
|
|
{
|
|
LogEvent(FORMAT(L"Deleting successfully uploaded source file \"%s\".", (Handle.FileName)));
|
|
|
|
DoDeleteLocalFile(Handle.FileName);
|
|
}
|
|
}
|
|
else if (CopyParam->ClearArchive && FLAGSET(Handle.Attrs, faArchive))
|
|
{
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
THROWOSIFFALSE(FileSetAttr(ApiPath(Handle.FileName), (Handle.Attrs & ~faArchive)) == 0);
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (Handle.FileName)));
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::Source(
|
|
const UnicodeString & FileName, const TSearchRecSmart * SearchRec,
|
|
const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags, TUploadSessionAction & Action, bool & ChildError)
|
|
{
|
|
UnicodeString ActionFileName = FileName;
|
|
if (CopyParam->OnTransferIn == NULL)
|
|
{
|
|
ActionFileName = ExpandUNCFileName(ActionFileName);
|
|
}
|
|
Action.FileName(ActionFileName);
|
|
|
|
OperationProgress->SetFile(FileName, false);
|
|
|
|
if (!AllowLocalFileTransfer(FileName, SearchRec, CopyParam, OperationProgress))
|
|
{
|
|
throw ESkipFile();
|
|
}
|
|
|
|
TLocalFileHandle Handle;
|
|
if (CopyParam->OnTransferIn == NULL)
|
|
{
|
|
OpenLocalFile(FileName, GENERIC_READ, Handle);
|
|
}
|
|
else
|
|
{
|
|
Handle.FileName = FileName;
|
|
}
|
|
|
|
OperationProgress->SetFileInProgress();
|
|
|
|
UnicodeString DestFileName =
|
|
ChangeFileName(CopyParam, ExtractFileName(FileName), osLocal, FLAGSET(Flags, tfFirstLevel));
|
|
|
|
if (Handle.Directory)
|
|
{
|
|
Action.Cancel();
|
|
DirectorySource(
|
|
IncludeTrailingBackslash(FileName), TargetDir, DestFileName, Handle.Attrs,
|
|
CopyParam, Params, OperationProgress, Flags);
|
|
}
|
|
else
|
|
{
|
|
if (CopyParam->OnTransferIn != NULL)
|
|
{
|
|
LogEvent(FORMAT(L"Streaming \"%s\" to remote directory started.", (FileName)));
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", (FileName)));
|
|
|
|
OperationProgress->SetLocalSize(Handle.Size);
|
|
|
|
// Suppose same data size to transfer as to read
|
|
// (not true with ASCII transfer)
|
|
OperationProgress->SetTransferSize(OperationProgress->LocalSize);
|
|
}
|
|
|
|
if (IsCapable[fcTextMode])
|
|
{
|
|
SelectSourceTransferMode(Handle, CopyParam);
|
|
}
|
|
|
|
FFileSystem->Source(
|
|
Handle, TargetDir, DestFileName, CopyParam, Params, OperationProgress, Flags, Action, ChildError);
|
|
|
|
LogFileDone(OperationProgress, AbsolutePath(TargetDir + DestFileName, true), Action);
|
|
OperationProgress->Succeeded();
|
|
}
|
|
|
|
Handle.Release();
|
|
|
|
UpdateSource(Handle, CopyParam, Params);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::CheckParallelFileTransfer(
|
|
const UnicodeString & TargetDir, TStringList * Files, const TCopyParamType * CopyParam, int Params,
|
|
UnicodeString & ParallelFileName, __int64 & ParallelFileSize, TFileOperationProgressType * OperationProgress)
|
|
{
|
|
if ((Configuration->ParallelTransferThreshold > 0) &&
|
|
FFileSystem->IsCapable(fcParallelFileTransfers))
|
|
{
|
|
TObject * ParallelObject = NULL;
|
|
if (TParallelOperation::GetOnlyFile(Files, ParallelFileName, ParallelObject))
|
|
{
|
|
TRemoteFile * File = static_cast<TRemoteFile *>(ParallelObject);
|
|
const TRemoteFile * UltimateFile = File->Resolve();
|
|
if ((UltimateFile == File) && // not tested with symlinks
|
|
(UltimateFile->Size >= static_cast<__int64>(Configuration->ParallelTransferThreshold) * 1024))
|
|
{
|
|
UnicodeString BaseFileName = GetBaseFileName(ParallelFileName);
|
|
TFileMasks::TParams MaskParams;
|
|
MaskParams.Size = UltimateFile->Size;
|
|
MaskParams.Modification = File->Modification;
|
|
if (!UseAsciiTransfer(BaseFileName, osRemote, CopyParam, MaskParams))
|
|
{
|
|
ParallelFileSize = UltimateFile->Size;
|
|
UnicodeString TargetFileName = CopyParam->ChangeFileName(UnixExtractFileName(ParallelFileName), osRemote, true);
|
|
UnicodeString DestFullName = CombinePaths(TargetDir, TargetFileName);
|
|
|
|
if (::FileExists(ApiPath(DestFullName)))
|
|
{
|
|
TSuspendFileOperationProgress Suspend(OperationProgress);
|
|
|
|
TOverwriteFileParams FileParams;
|
|
__int64 MTime;
|
|
OpenLocalFile(DestFullName, GENERIC_READ, NULL, NULL, NULL, &MTime, NULL, &FileParams.DestSize, false);
|
|
FileParams.SourceSize = ParallelFileSize;
|
|
FileParams.SourceTimestamp = UltimateFile->Modification;
|
|
FileParams.SourcePrecision = UltimateFile->ModificationFmt;
|
|
FileParams.DestTimestamp = UnixToDateTime(MTime, SessionData->DSTMode);
|
|
int Answers = qaYes | qaNo | qaCancel;
|
|
TQueryParams QueryParams(qpNeverAskAgainCheck);
|
|
unsigned int Answer =
|
|
ConfirmFileOverwrite(
|
|
ParallelFileName, TargetFileName, &FileParams, Answers, &QueryParams, osRemote,
|
|
CopyParam, Params, OperationProgress, EmptyStr);
|
|
switch (Answer)
|
|
{
|
|
case qaCancel:
|
|
case qaNo:
|
|
OperationProgress->SetCancelAtLeast(csCancel);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CopyToLocal(
|
|
TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
|
|
TParallelOperation * ParallelOperation)
|
|
{
|
|
DebugAssert(FFileSystem);
|
|
|
|
// see scp.c: sink(), tolocal()
|
|
|
|
bool Result = false;
|
|
DebugAssert(FilesToCopy != NULL);
|
|
TOnceDoneOperation OnceDoneOperation = odoIdle;
|
|
|
|
if ((CopyParam->OnTransferOut != NULL) && !FFileSystem->IsCapable(fcTransferOut))
|
|
{
|
|
NotSupported();
|
|
}
|
|
|
|
FDestFileName = L"";
|
|
FMultipleDestinationFiles = false;
|
|
|
|
BeginTransaction();
|
|
try
|
|
{
|
|
__int64 TotalSize;
|
|
bool TotalSizeKnown = false;
|
|
FLastProgressLogged = GetTickCount();
|
|
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
|
|
|
|
std::unique_ptr<TStringList> Files;
|
|
bool ACanParallel =
|
|
CanParallel(CopyParam, Params, ParallelOperation) &&
|
|
DebugAlwaysTrue(CopyParam->OnTransferOut == NULL);
|
|
if (ACanParallel)
|
|
{
|
|
Files.reset(new TStringList());
|
|
Files->OwnsObjects = true;
|
|
}
|
|
|
|
if ((CopyParam->Size >= 0) &&
|
|
DebugAlwaysTrue(!ACanParallel)) // Size is set for sync only and we never use parallel transfer for sync
|
|
{
|
|
TotalSize = CopyParam->Size;
|
|
TotalSizeKnown = true;
|
|
}
|
|
else
|
|
{
|
|
ExceptionOnFail = true;
|
|
try
|
|
{
|
|
TCalculateSizeStats Stats;
|
|
Stats.FoundFiles = Files.get();
|
|
TCalculateSizeParams Params;
|
|
Params.Params = csIgnoreErrors;
|
|
Params.CopyParam = CopyParam;
|
|
Params.AllowDirs = ACanParallel || CopyParam->CalculateSize;
|
|
Params.Stats = &Stats;
|
|
if (CalculateFilesSize(FilesToCopy, TotalSize, Params))
|
|
{
|
|
TotalSizeKnown = true;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
ExceptionOnFail = false;
|
|
}
|
|
}
|
|
|
|
OperationStart(OperationProgress, (Params & cpDelete ? foMove : foCopy), osRemote,
|
|
FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit, CopyParam->OnceDoneOperation);
|
|
|
|
bool CollectingUsage = false;
|
|
try
|
|
{
|
|
if (TotalSizeKnown)
|
|
{
|
|
if (Configuration->Usage->Collect)
|
|
{
|
|
int CounterTotalSize = TUsage::CalculateCounterSize(TotalSize);
|
|
Configuration->Usage->Inc(L"Downloads");
|
|
Configuration->Usage->IncAndSetMax(L"DownloadedBytes", L"MaxDownloadSize", CounterTotalSize);
|
|
CollectingUsage = true;
|
|
}
|
|
|
|
OperationProgress.SetTotalSize(TotalSize);
|
|
}
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
bool Parallel = ACanParallel && TotalSizeKnown;
|
|
LogTotalTransferDetails(TargetDir, CopyParam, &OperationProgress, Parallel, Files.get());
|
|
|
|
if (Parallel)
|
|
{
|
|
UnicodeString ParallelFileName;
|
|
__int64 ParallelFileSize = -1;
|
|
CheckParallelFileTransfer(TargetDir, Files.get(), CopyParam, Params, ParallelFileName, ParallelFileSize, &OperationProgress);
|
|
|
|
if (OperationProgress.Cancel == csContinue)
|
|
{
|
|
if (ParallelFileSize >= 0)
|
|
{
|
|
DebugAssert(ParallelFileSize == TotalSize);
|
|
Params |= cpNoConfirmation;
|
|
}
|
|
|
|
// OnceDoneOperation is not supported
|
|
ParallelOperation->Init(
|
|
Files.release(), TargetDir, CopyParam, Params, &OperationProgress, Log->Name, ParallelFileSize);
|
|
UnicodeString ParallelFilePrefix;
|
|
CopyParallel(ParallelOperation, &OperationProgress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FFileSystem->CopyToLocal(FilesToCopy, TargetDir, CopyParam, Params,
|
|
&OperationProgress, OnceDoneOperation);
|
|
}
|
|
|
|
LogTotalTransferDone(&OperationProgress);
|
|
}
|
|
__finally
|
|
{
|
|
if (Active)
|
|
{
|
|
ReactOnCommand(fsCopyToLocal);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception &E)
|
|
{
|
|
CommandError(&E, MainInstructions(LoadStr(TOLOCAL_COPY_ERROR)));
|
|
OnceDoneOperation = odoIdle;
|
|
}
|
|
|
|
if (OperationProgress.Cancel == csContinue)
|
|
{
|
|
Result = true;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (CollectingUsage)
|
|
{
|
|
Configuration->Usage->IncAndSetMax(L"DownloadTime", L"MaxDownloadTime", TimeToSeconds(OperationProgress.TimeElapsed()));
|
|
}
|
|
OperationStop(OperationProgress);
|
|
}
|
|
Files.reset(NULL);
|
|
}
|
|
__finally
|
|
{
|
|
// If session is still active (no fatal error) we reload directory
|
|
// by calling EndTransaction
|
|
EndTransaction();
|
|
}
|
|
|
|
if (OnceDoneOperation != odoIdle)
|
|
{
|
|
UnicodeString DestFileName;
|
|
if (!FMultipleDestinationFiles)
|
|
{
|
|
DestFileName = FDestFileName;
|
|
}
|
|
CloseOnCompletion(OnceDoneOperation, UnicodeString(), TargetDir, DestFileName);
|
|
}
|
|
|
|
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];
|
|
const TRemoteFile * File = dynamic_cast<const TRemoteFile *>(FilesToCopy->Objects[Index]);
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
UnicodeString AbsoluteFileName = AbsolutePath(FileName, true);
|
|
SinkRobust(AbsoluteFileName, File, FullTargetDir, CopyParam, Params, OperationProgress, Flags | tfFirstLevel);
|
|
Success = true;
|
|
}
|
|
catch (ESkipFile & E)
|
|
{
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
OperationFinish(OperationProgress, File, 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);
|
|
try
|
|
{
|
|
bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
|
|
bool CanRetry = (CopyParam->OnTransferOut == NULL);
|
|
TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny, CanRetry);
|
|
bool Sunk = false;
|
|
|
|
do
|
|
{
|
|
try
|
|
{
|
|
// If connection is lost while deleting the file, so not retry download on the next round.
|
|
// The file may not exist anymore and the download attempt might overwrite the (only) local copy.
|
|
if (!Sunk)
|
|
{
|
|
Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress, Flags, Action);
|
|
Sunk = true;
|
|
}
|
|
|
|
if (FLAGSET(Params, cpDelete))
|
|
{
|
|
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;
|
|
DeleteFile(FileName, File, &Params);
|
|
}
|
|
}
|
|
catch (Exception & E)
|
|
{
|
|
if (!RobustLoop.TryReopen(E))
|
|
{
|
|
if (!Sunk)
|
|
{
|
|
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());
|
|
}
|
|
__finally
|
|
{
|
|
// Once we issue <download> we must terminate the data stream
|
|
if (Action.IsValid() && (CopyParam->OnTransferOut != NULL) && DebugAlwaysTrue(IsCapable[fcTransferOut]))
|
|
{
|
|
CopyParam->OnTransferOut(this, NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
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)
|
|
{
|
|
Action.FileName(FileName);
|
|
DebugAssert(File);
|
|
|
|
if (!DoAllowRemoteFileTransfer(File, CopyParam, false))
|
|
{
|
|
LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
|
|
throw ESkipFile();
|
|
}
|
|
|
|
if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
|
|
{
|
|
OperationProgress->AddSkippedFileSize(File->Resolve()->Size);
|
|
throw ESkipFile();
|
|
}
|
|
|
|
LogFileDetails(FileName, File->Modification, File->Size, File->LinkedFile);
|
|
|
|
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 skipped.
|
|
// Throw "skip file" for the directory to avoid attempt to deletion
|
|
// of any parent directory
|
|
if (FLAGSET(Params, cpDelete) && SinkFileParams.Skipped)
|
|
{
|
|
throw ESkipFile();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CopyParam->OnTransferOut != NULL)
|
|
{
|
|
LogEvent(FORMAT(L"Streaming \"%s\" to local machine started.", (FileName)));
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
|
|
}
|
|
|
|
// Will we use ASCII or BINARY file transfer?
|
|
UnicodeString BaseFileName = GetBaseFileName(FileName);
|
|
const TRemoteFile * UltimateFile = File->Resolve();
|
|
TFileMasks::TParams MaskParams;
|
|
MaskParams.Size = UltimateFile->Size;
|
|
MaskParams.Modification = File->Modification;
|
|
SelectTransferMode(BaseFileName, osRemote, CopyParam, MaskParams);
|
|
|
|
// Suppose same data size to transfer as to write
|
|
// (not true with ASCII transfer)
|
|
__int64 TransferSize = (CopyParam->PartSize >= 0) ? CopyParam->PartSize : (UltimateFile->Size - std::max(0LL, CopyParam->PartOffset));
|
|
OperationProgress->SetLocalSize(TransferSize);
|
|
if (IsFileEncrypted(FileName))
|
|
{
|
|
TransferSize += TEncryption::GetOverhead();
|
|
}
|
|
OperationProgress->SetTransferSize(TransferSize);
|
|
|
|
int Attrs;
|
|
UnicodeString LogFileName;
|
|
if (CopyParam->OnTransferOut != NULL)
|
|
{
|
|
Attrs = -1;
|
|
LogFileName = L"-";
|
|
}
|
|
else
|
|
{
|
|
FILE_OPERATION_LOOP_BEGIN
|
|
{
|
|
Attrs = FileGetAttrFix(ApiPath(DestFullName));
|
|
if ((Attrs >= 0) && FLAGSET(Attrs, faDirectory))
|
|
{
|
|
EXCEPTION;
|
|
}
|
|
}
|
|
FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
|
|
LogFileName = ExpandUNCFileName(DestFullName);
|
|
}
|
|
|
|
FFileSystem->Sink(
|
|
FileName, File, TargetDir, DestFileName, Attrs, CopyParam, Params, OperationProgress, Flags, Action);
|
|
|
|
LogFileDone(OperationProgress, LogFileName, Action);
|
|
OperationProgress->Succeeded();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
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, TModificationFmt ModificationFmt, TDSTMode DSTMode)
|
|
{
|
|
if (ModificationFmt == mfNone)
|
|
{
|
|
LogEvent(L"Timestamp not known");
|
|
}
|
|
else
|
|
{
|
|
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 (ESkipFile & E)
|
|
{
|
|
Params->Skipped = true;
|
|
|
|
if (!HandleException(&E))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
if (Params->OperationProgress->Cancel)
|
|
{
|
|
Abort();
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::ReflectSettings()
|
|
{
|
|
DebugAssert(FLog != NULL);
|
|
FLog->ReflectSettings();
|
|
if (FActionLogOwned)
|
|
{
|
|
DebugAssert(FActionLog != NULL);
|
|
FActionLog->ReflectSettings();
|
|
}
|
|
// also FTunnelLog ?
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CollectUsage()
|
|
{
|
|
switch (SessionData->FSProtocol)
|
|
{
|
|
case fsSCPonly:
|
|
Configuration->Usage->Inc(L"OpenedSessionsSCP");
|
|
break;
|
|
|
|
case fsSFTP:
|
|
case fsSFTPonly:
|
|
Configuration->Usage->Inc(L"OpenedSessionsSFTP");
|
|
break;
|
|
|
|
case fsFTP:
|
|
if (SessionData->Ftps == ftpsNone)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsFTP");
|
|
}
|
|
else
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsFTPS");
|
|
}
|
|
break;
|
|
|
|
case fsWebDAV:
|
|
if (SessionData->Ftps == ftpsNone)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsWebDAV");
|
|
}
|
|
else
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsWebDAVS");
|
|
}
|
|
break;
|
|
|
|
case fsS3:
|
|
Configuration->Usage->Inc(L"OpenedSessionsS3");
|
|
break;
|
|
}
|
|
|
|
if (Configuration->Logging && Configuration->LogToFile)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsLogToFile2");
|
|
}
|
|
|
|
if (Configuration->LogActions)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsXmlLog");
|
|
}
|
|
|
|
std::unique_ptr<TSessionData> FactoryDefaults(new TSessionData(L""));
|
|
if (!SessionData->IsSame(FactoryDefaults.get(), true))
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsAdvanced");
|
|
}
|
|
|
|
if (SessionData->ProxyMethod != ::pmNone)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsProxy");
|
|
}
|
|
if (SessionData->FtpProxyLogonType > 0)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsFtpProxy");
|
|
}
|
|
if (IsEncryptingFiles())
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsEncrypted");
|
|
}
|
|
|
|
if (SessionData->SendBuf == 0)
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsNoSendBuf");
|
|
}
|
|
|
|
FCollectFileSystemUsage = true;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
static UnicodeString __fastcall FormatCertificateData(const UnicodeString & Fingerprint, int Failures)
|
|
{
|
|
return FORMAT(L"%s;%2.2X", (Fingerprint, Failures));
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::VerifyCertificateAgainstSessionData(
|
|
const UnicodeString & FingerprintSHA1, const UnicodeString & FingerprintSHA256,
|
|
const UnicodeString & CertificateSubject)
|
|
{
|
|
bool Result = false;
|
|
UnicodeString Buf = SessionData->HostKey;
|
|
while (!Result && !Buf.IsEmpty())
|
|
{
|
|
UnicodeString ExpectedKey = CutToChar(Buf, L';', false);
|
|
if (ExpectedKey == L"*")
|
|
{
|
|
UnicodeString Message = LoadStr(ANY_CERTIFICATE);
|
|
Information(Message);
|
|
Log->Add(llException, Message);
|
|
Result = true;
|
|
}
|
|
else if (SameChecksum(ExpectedKey, FingerprintSHA1, false) ||
|
|
SameChecksum(ExpectedKey, FingerprintSHA256, false))
|
|
{
|
|
LogEvent(FORMAT(L"Certificate for \"%s\" matches configured fingerprint", (CertificateSubject)));
|
|
Result = true;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::VerifyCertificate(
|
|
const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
|
|
const UnicodeString & FingerprintSHA1, const UnicodeString & FingerprintSHA256,
|
|
const UnicodeString & CertificateSubject, int Failures)
|
|
{
|
|
bool Result = false;
|
|
|
|
UnicodeString CertificateDataSHA1 = FormatCertificateData(FingerprintSHA1, Failures);
|
|
UnicodeString CertificateDataSHA256 = FormatCertificateData(FingerprintSHA256, Failures);
|
|
|
|
std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
|
|
Storage->AccessMode = smRead;
|
|
|
|
if (Storage->OpenSubKey(CertificateStorageKey, false))
|
|
{
|
|
if (Storage->ValueExists(SiteKey))
|
|
{
|
|
UnicodeString CachedCertificateData = Storage->ReadString(SiteKey, L"");
|
|
if (SameChecksum(CertificateDataSHA1, CachedCertificateData, false) ||
|
|
SameChecksum(CertificateDataSHA256, CachedCertificateData, false))
|
|
{
|
|
LogEvent(FORMAT(L"Certificate for \"%s\" matches cached fingerprint and failures", (CertificateSubject)));
|
|
Result = true;
|
|
}
|
|
}
|
|
else if (Storage->ValueExists(FingerprintSHA1) || Storage->ValueExists(FingerprintSHA256))
|
|
{
|
|
LogEvent(FORMAT(L"Certificate for \"%s\" matches legacy cached fingerprint", (CertificateSubject)));
|
|
Result = true;
|
|
}
|
|
}
|
|
|
|
if (!Result)
|
|
{
|
|
Result = VerifyCertificateAgainstSessionData(FingerprintSHA1, FingerprintSHA256, CertificateSubject);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::ConfirmCertificate(TSessionInfo & SessionInfo, int Failures, const UnicodeString & CertificateStorageKey)
|
|
{
|
|
TClipboardHandler ClipboardHandler;
|
|
ClipboardHandler.Text =
|
|
FORMAT(L"SHA-256: %s\nSHA-1: %s", (SessionInfo.CertificateFingerprintSHA256, SessionInfo.CertificateFingerprintSHA1));
|
|
|
|
TQueryButtonAlias Aliases[1];
|
|
Aliases[0].Button = qaRetry;
|
|
Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
|
|
Aliases[0].ActionAlias = LoadStr(COPY_CERTIFICATE_ACTION);
|
|
Aliases[0].OnSubmit = &ClipboardHandler.Copy;
|
|
|
|
TQueryParams Params(qpWaitInBatch);
|
|
Params.HelpKeyword = HELP_VERIFY_CERTIFICATE;
|
|
Params.NoBatchAnswers = qaYes | qaRetry;
|
|
Params.Aliases = Aliases;
|
|
Params.AliasesCount = std::size(Aliases);
|
|
unsigned int Answer =
|
|
QueryUser(
|
|
FMTLOAD(VERIFY_CERT_PROMPT3, (SessionInfo.Certificate)),
|
|
NULL, qaYes | qaNo | qaCancel | qaRetry, &Params, qtWarning);
|
|
|
|
bool Result;
|
|
switch (Answer)
|
|
{
|
|
case qaYes:
|
|
CacheCertificate(
|
|
CertificateStorageKey, SessionData->SiteKey,
|
|
SessionInfo.CertificateFingerprintSHA1, SessionInfo.CertificateFingerprintSHA256, Failures);
|
|
Result = true;
|
|
break;
|
|
|
|
case qaNo:
|
|
Result = true;
|
|
break;
|
|
|
|
case qaCancel:
|
|
Configuration->Usage->Inc(L"HostNotVerified");
|
|
Result = false;
|
|
break;
|
|
|
|
default:
|
|
DebugFail();
|
|
Result = false;
|
|
break;
|
|
}
|
|
|
|
// Cache only if the certificate was accepted manually
|
|
if (Result)
|
|
{
|
|
Configuration->RememberLastFingerprint(
|
|
SessionData->SiteKey, TlsFingerprintType, SessionInfo.CertificateFingerprintSHA256);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CacheCertificate(
|
|
const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
|
|
const UnicodeString & DebugUsedArg(FingerprintSHA1), const UnicodeString & FingerprintSHA256, int Failures)
|
|
{
|
|
UnicodeString CertificateData = FormatCertificateData(FingerprintSHA256, Failures);
|
|
|
|
std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
|
|
Storage->AccessMode = smReadWrite;
|
|
|
|
if (Storage->OpenSubKey(CertificateStorageKey, true))
|
|
{
|
|
Storage->WriteString(SiteKey, CertificateData);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
// Shared implementation for WebDAV and S3
|
|
bool TTerminal::VerifyOrConfirmHttpCertificate(
|
|
const UnicodeString & AHostName, int APortNumber, const TNeonCertificateData & AData,
|
|
TSessionInfo & SessionInfo)
|
|
{
|
|
TNeonCertificateData Data = AData;
|
|
SessionInfo.CertificateFingerprintSHA1 = Data.FingerprintSHA1;
|
|
SessionInfo.CertificateFingerprintSHA256 = Data.FingerprintSHA256;
|
|
|
|
bool Result;
|
|
if (SessionData->FingerprintScan)
|
|
{
|
|
Result = false;
|
|
}
|
|
else
|
|
{
|
|
LogEvent(0, CertificateVerificationMessage(Data));
|
|
|
|
Result = VerifyCertificateAgainstSessionData(Data.FingerprintSHA1, Data.FingerprintSHA256, Data.Subject);
|
|
if (Result)
|
|
{
|
|
SessionInfo.CertificateVerifiedManually = true;
|
|
// Em-Dash - don't know status of the certificate and it won't typically be shown anyway
|
|
UnicodeString Summary = L"\u2014";
|
|
SessionInfo.Certificate = CertificateSummary(Data, AHostName, Summary);
|
|
}
|
|
else
|
|
{
|
|
UnicodeString WindowsValidatedMessage;
|
|
// Side effect is that NE_SSL_UNTRUSTED is removed from Failure, before we call VerifyCertificate,
|
|
// as it compares failures against a cached failures that do not include NE_SSL_UNTRUSTED
|
|
// (if the certificate is trusted by Windows certificate store).
|
|
// But we will log that result only if we actually use it for the decision.
|
|
bool WindowsValidated = NeonWindowsValidateCertificateWithMessage(Data, WindowsValidatedMessage);
|
|
|
|
UnicodeString SiteKey = TSessionData::FormatSiteKey(AHostName, APortNumber);
|
|
Result =
|
|
VerifyCertificate(
|
|
HttpsCertificateStorageKey, SiteKey, Data.FingerprintSHA1, Data.FingerprintSHA256, Data.Subject, Data.Failures);
|
|
|
|
if (Result)
|
|
{
|
|
SessionInfo.CertificateVerifiedManually = true;
|
|
}
|
|
else
|
|
{
|
|
Result = WindowsValidated;
|
|
LogEvent(0, WindowsValidatedMessage);
|
|
}
|
|
|
|
SessionInfo.Certificate = CertificateSummary(Data, AHostName);
|
|
|
|
if (!Result)
|
|
{
|
|
if (ConfirmCertificate(SessionInfo, Data.Failures, HttpsCertificateStorageKey))
|
|
{
|
|
Result = true;
|
|
SessionInfo.CertificateVerifiedManually = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminal::CollectTlsUsage(const UnicodeString & TlsVersionStr)
|
|
{
|
|
// see SSL_get_version() in OpenSSL ssl_lib.c
|
|
if (TlsVersionStr == L"TLSv1.3")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsTLS13");
|
|
}
|
|
else if (TlsVersionStr == L"TLSv1.2")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsTLS12");
|
|
}
|
|
else if (TlsVersionStr == L"TLSv1.1")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsTLS11");
|
|
}
|
|
else if (TlsVersionStr == L"TLSv1")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsTLS10");
|
|
}
|
|
else if (TlsVersionStr == L"SSLv3")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsSSL30");
|
|
}
|
|
else if (TlsVersionStr == L"SSLv2")
|
|
{
|
|
Configuration->Usage->Inc(L"OpenedSessionsSSL20");
|
|
}
|
|
else
|
|
{
|
|
DebugFail();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::LoadTlsCertificate(X509 *& Certificate, EVP_PKEY *& PrivateKey)
|
|
{
|
|
bool Result = !SessionData->TlsCertificateFile.IsEmpty();
|
|
if (Result)
|
|
{
|
|
UnicodeString Passphrase = SessionData->Passphrase;
|
|
|
|
// Inspired by neon's ne_ssl_clicert_read
|
|
bool Retry;
|
|
|
|
do
|
|
{
|
|
Retry = false;
|
|
|
|
bool WrongPassphrase;
|
|
ParseCertificate(SessionData->TlsCertificateFile, Passphrase, Certificate, PrivateKey, WrongPassphrase);
|
|
if (WrongPassphrase)
|
|
{
|
|
if (Passphrase.IsEmpty())
|
|
{
|
|
LogEvent(L"Certificate is encrypted, need passphrase");
|
|
Information(LoadStr(CLIENT_CERTIFICATE_LOADING));
|
|
}
|
|
else
|
|
{
|
|
Information(LoadStr(CERTIFICATE_DECODE_ERROR_INFO));
|
|
}
|
|
|
|
Passphrase = L"";
|
|
if (PromptUser(
|
|
SessionData, pkPassphrase,
|
|
LoadStr(CERTIFICATE_PASSPHRASE_TITLE), L"",
|
|
LoadStr(CERTIFICATE_PASSPHRASE_PROMPT), false, 0, Passphrase))
|
|
{
|
|
Retry = true;
|
|
}
|
|
else
|
|
{
|
|
Result = false;
|
|
}
|
|
}
|
|
}
|
|
while (Retry);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::GetBaseFileName(UnicodeString FileName)
|
|
{
|
|
if (FSessionData->TrimVMSVersions)
|
|
{
|
|
int P = FileName.LastDelimiter(L";");
|
|
if (P > 0)
|
|
{
|
|
FileName.SetLength(P - 1);
|
|
}
|
|
}
|
|
return FileName;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::ChangeFileName(const TCopyParamType * CopyParam,
|
|
UnicodeString FileName, TOperationSide Side, bool FirstLevel)
|
|
{
|
|
FileName = GetBaseFileName(FileName);
|
|
FileName = CopyParam->ChangeFileName(FileName, Side, FirstLevel);
|
|
return FileName;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::CanRecurseToDirectory(const TRemoteFile * File)
|
|
{
|
|
return !File->IsSymLink || FSessionData->FollowDirectorySymlinks;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal::TEncryptedFileNames::const_iterator __fastcall TTerminal::GetEncryptedFileName(const UnicodeString & Path)
|
|
{
|
|
UnicodeString FileDir = UnixExtractFileDir(Path);
|
|
|
|
// If we haven't been in this folder yet, read it to collect mapping to encrypted file names.
|
|
if (FFoldersScannedForEncryptedFiles.find(FileDir) == FFoldersScannedForEncryptedFiles.end())
|
|
{
|
|
try
|
|
{
|
|
delete DoReadDirectoryListing(FileDir, true);
|
|
}
|
|
catch (Exception &)
|
|
{
|
|
if (!Active)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
if (FEncryptedFileNames.find(Path) == FEncryptedFileNames.end())
|
|
{
|
|
FEncryptedFileNames.insert(std::make_pair(Path, UnixExtractFileName(Path)));
|
|
LogEvent(2, FORMAT(L"Name of file '%s' assumed not to be encrypted", (Path)));
|
|
}
|
|
}
|
|
|
|
FFoldersScannedForEncryptedFiles.insert(FileDir);
|
|
}
|
|
|
|
TEncryptedFileNames::const_iterator Result = FEncryptedFileNames.find(Path);
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TTerminal::IsFileEncrypted(const UnicodeString & Path, bool EncryptNewFiles)
|
|
{
|
|
// can be optimized
|
|
bool Result = (EncryptFileName(Path, EncryptNewFiles) != Path);
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::EncryptFileName(const UnicodeString & Path, bool EncryptNewFiles)
|
|
{
|
|
UnicodeString Result = Path;
|
|
if (IsEncryptingFiles() && !IsUnixRootPath(Path))
|
|
{
|
|
UnicodeString FileName = UnixExtractFileName(Path);
|
|
UnicodeString FileDir = UnixExtractFileDir(Path);
|
|
|
|
if (!FileName.IsEmpty() && IsRealFile(FileName))
|
|
{
|
|
TEncryptedFileNames::const_iterator I = GetEncryptedFileName(Path);
|
|
if (I != FEncryptedFileNames.end())
|
|
{
|
|
FileName = I->second;
|
|
}
|
|
else if (EncryptNewFiles)
|
|
{
|
|
TEncryption Encryption(FEncryptKey);
|
|
FileName = Encryption.EncryptFileName(FileName);
|
|
FEncryptedFileNames.insert(std::make_pair(Path, FileName));
|
|
LogEvent(2, FORMAT(L"Name of file '%s' encrypted as '%s'", (Path, FileName)));
|
|
}
|
|
}
|
|
|
|
FileDir = EncryptFileName(FileDir, EncryptNewFiles);
|
|
Result = UnixCombinePathsForce(FileDir, FileName);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall TTerminal::DecryptFileName(const UnicodeString & Path, bool DecryptFullPath, bool DontCache)
|
|
{
|
|
UnicodeString Result = Path;
|
|
if (IsEncryptingFiles() && !IsUnixRootPath(Path))
|
|
{
|
|
UnicodeString FileName = UnixExtractFileName(Path);
|
|
UnicodeString FileNameEncrypted = FileName;
|
|
|
|
bool Encrypted = TEncryption::IsEncryptedFileName(FileName);
|
|
if (Encrypted)
|
|
{
|
|
TEncryption Encryption(FEncryptKey);
|
|
FileName = Encryption.DecryptFileName(FileName);
|
|
}
|
|
|
|
if (Encrypted || DecryptFullPath) // DecryptFullPath is just an optimization
|
|
{
|
|
UnicodeString FileDir = UnixExtractFileDir(Path);
|
|
FileDir = DecryptFileName(FileDir, DecryptFullPath, DontCache);
|
|
Result = UnixCombinePaths(FileDir, FileName);
|
|
}
|
|
|
|
TEncryptedFileNames::iterator Iter = FEncryptedFileNames.find(Result);
|
|
bool NotCached = (Iter == FEncryptedFileNames.end());
|
|
if (!DontCache && (Encrypted || NotCached))
|
|
{
|
|
if (Encrypted && (NotCached || (Iter->second != FileNameEncrypted)))
|
|
{
|
|
LogEvent(2, FORMAT(L"Name of file '%s' decrypted from '%s'", (Result, FileNameEncrypted)));
|
|
}
|
|
// This may overwrite another variant of encryption
|
|
FEncryptedFileNames[Result] = FileNameEncrypted;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TRemoteFile * TTerminal::CheckRights(const UnicodeString & EntryType, const UnicodeString & FileName, bool & WrongRights)
|
|
{
|
|
std::unique_ptr<TRemoteFile> File;
|
|
try
|
|
{
|
|
LogEvent(FORMAT(L"Checking %s \"%s\"...", (LowerCase(EntryType), FileName)));
|
|
File.reset(ReadFile(FileName));
|
|
int ForbiddenRights = TRights::rfGroupWrite | TRights::rfOtherWrite;
|
|
if ((File->Rights->Number & ForbiddenRights) != 0)
|
|
{
|
|
LogEvent(FORMAT(L"%s \"%s\" exists, but has incorrect permissions %s.", (EntryType, FileName, File->Rights->Octal)));
|
|
WrongRights = true;
|
|
}
|
|
else
|
|
{
|
|
LogEvent(FORMAT(L"%s \"%s\" exists and has correct permissions %s.", (EntryType, FileName, File->Rights->Octal)));
|
|
}
|
|
}
|
|
catch (Exception &)
|
|
{
|
|
}
|
|
return File.release();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TTerminal::LogAndInformation(const UnicodeString & S)
|
|
{
|
|
LogEvent(S);
|
|
Information(S);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
|
|
{
|
|
if (FSProtocol != cfsSFTP)
|
|
{
|
|
NotSupported();
|
|
}
|
|
|
|
UnicodeString Result;
|
|
|
|
UnicodeString TemporaryDir;
|
|
bool PrevAutoReadDirectory = AutoReadDirectory;
|
|
bool PrevExceptionOnFail = ExceptionOnFail;
|
|
|
|
UnicodeString AuthorizedKeysFilePath = FORMAT(L"%s/%s", (OpensshFolderName, OpensshAuthorizedKeysFileName));
|
|
|
|
try
|
|
{
|
|
AutoReadDirectory = false;
|
|
ExceptionOnFail = true;
|
|
|
|
Log->AddSeparator();
|
|
|
|
UnicodeString Comment;
|
|
bool UnusedHasCertificate;
|
|
UnicodeString Line = GetPublicKeyLine(FileName, Comment, UnusedHasCertificate);
|
|
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_ADDING, (AuthorizedKeysFilePath)) + L"\n" + Line);
|
|
|
|
UnicodeString SshFolderAbsolutePath = UnixIncludeTrailingBackslash(GetHomeDirectory()) + OpensshFolderName;
|
|
bool WrongRights = false;
|
|
std::unique_ptr<TRemoteFile> SshFolderFile(CheckRights(L"Folder", SshFolderAbsolutePath, WrongRights));
|
|
if (SshFolderFile.get() == NULL)
|
|
{
|
|
TRights SshFolderRights;
|
|
SshFolderRights.Number = TRights::rfUserRead | TRights::rfUserWrite | TRights::rfUserExec;
|
|
TRemoteProperties SshFolderProperties;
|
|
SshFolderProperties.Rights = SshFolderRights;
|
|
SshFolderProperties.Valid = TValidProperties() << vpRights;
|
|
|
|
LogEvent(FORMAT(L"Trying to create \"%s\" folder with permissions %s...", (OpensshFolderName, SshFolderRights.Octal)));
|
|
CreateDirectory(SshFolderAbsolutePath, &SshFolderProperties);
|
|
}
|
|
|
|
TemporaryDir = ExcludeTrailingBackslash(Configuration->TemporaryDir());
|
|
if (!ForceDirectories(ApiPath(TemporaryDir)))
|
|
{
|
|
throw EOSExtException(FMTLOAD(CREATE_TEMP_DIR_ERROR, (TemporaryDir)));
|
|
}
|
|
UnicodeString TemporaryAuthorizedKeysFile = IncludeTrailingBackslash(TemporaryDir) + OpensshAuthorizedKeysFileName;
|
|
|
|
UnicodeString AuthorizedKeysFileAbsolutePath = UnixIncludeTrailingBackslash(SshFolderAbsolutePath) + OpensshAuthorizedKeysFileName;
|
|
|
|
bool Updated = true;
|
|
TCopyParamType CopyParam; // Use factory defaults
|
|
CopyParam.ResumeSupport = rsOff; // not to break the permissions
|
|
CopyParam.PreserveTime = false; // not needed
|
|
|
|
UnicodeString AuthorizedKeys;
|
|
std::unique_ptr<TRemoteFile> AuthorizedKeysFileFile(CheckRights(L"File", AuthorizedKeysFileAbsolutePath, WrongRights));
|
|
if (AuthorizedKeysFileFile.get() != NULL)
|
|
{
|
|
AuthorizedKeysFileFile->FullFileName = AuthorizedKeysFileAbsolutePath;
|
|
std::unique_ptr<TStrings> Files(new TStringList());
|
|
Files->AddObject(AuthorizedKeysFileAbsolutePath, AuthorizedKeysFileFile.get());
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_DOWNLOADING, (OpensshAuthorizedKeysFileName)));
|
|
CopyToLocal(Files.get(), TemporaryDir, &CopyParam, cpNoConfirmation, NULL);
|
|
// Overload with Encoding parameter work incorrectly, when used on a file without BOM
|
|
AuthorizedKeys = TFile::ReadAllText(TemporaryAuthorizedKeysFile);
|
|
|
|
std::unique_ptr<TStrings> AuthorizedKeysLines(TextToStringList(AuthorizedKeys));
|
|
int P = Line.Pos(L" ");
|
|
if (DebugAlwaysTrue(P > 0))
|
|
{
|
|
P = PosEx(L" ", Line, P + 1);
|
|
}
|
|
UnicodeString Prefix = Line.SubString(1, P); // including the space
|
|
for (int Index = 0; Index < AuthorizedKeysLines->Count; Index++)
|
|
{
|
|
if (StartsStr(Prefix, AuthorizedKeysLines->Strings[Index]))
|
|
{
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_CONTAINS, (OpensshAuthorizedKeysFileName)) + L"\n" + AuthorizedKeysLines->Strings[Index]);
|
|
Updated = false;
|
|
}
|
|
}
|
|
|
|
if (Updated)
|
|
{
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_NOT_CONTAINS, (OpensshAuthorizedKeysFileName)));
|
|
if (!EndsStr(L"\n", AuthorizedKeys))
|
|
{
|
|
LogEvent(FORMAT(L"Adding missing trailing new line to \"%s\" file...", (OpensshAuthorizedKeysFileName)));
|
|
AuthorizedKeys += L"\n";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_NEW, (OpensshAuthorizedKeysFileName)));
|
|
CopyParam.PreserveRights = true;
|
|
CopyParam.Rights.Number = TRights::rfUserRead | TRights::rfUserWrite;
|
|
}
|
|
|
|
if (Updated)
|
|
{
|
|
AuthorizedKeys += Line + L"\n";
|
|
// Overload without Encoding parameter uses TEncoding::UTF8, but does not write BOM, what we want
|
|
TFile::WriteAllText(TemporaryAuthorizedKeysFile, AuthorizedKeys);
|
|
std::unique_ptr<TStrings> Files(new TStringList());
|
|
Files->Add(TemporaryAuthorizedKeysFile);
|
|
LogAndInformation(FMTLOAD(PUBLIC_KEY_UPLOADING, (OpensshAuthorizedKeysFileName)));
|
|
CopyToRemote(Files.get(), SshFolderAbsolutePath, &CopyParam, cpNoConfirmation, NULL);
|
|
}
|
|
|
|
Result = FMTLOAD(PUBLIC_KEY_UPLOADED, (Comment));
|
|
if (Updated)
|
|
{
|
|
LogAndInformation(ReplaceStr(RemoveMainInstructionsTag(Result), L"\n\n", L"\n"));
|
|
}
|
|
if (WrongRights)
|
|
{
|
|
UnicodeString PermissionsInfo = FMTLOAD(PUBLIC_KEY_PERMISSIONS, (AuthorizedKeysFilePath));
|
|
LogAndInformation(PermissionsInfo);
|
|
Result += L"\n\n" + PermissionsInfo;
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
AutoReadDirectory = PrevAutoReadDirectory;
|
|
ExceptionOnFail = PrevExceptionOnFail;
|
|
if (!TemporaryDir.IsEmpty())
|
|
{
|
|
RecursiveDeleteFile(ExcludeTrailingBackslash(TemporaryDir));
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool TTerminal::IsValidFile(TRemoteFile * File)
|
|
{
|
|
return
|
|
!File->FileName.IsEmpty() &&
|
|
(IsUnixRootPath(File->FileName) || UnixExtractFileDir(File->FileName).IsEmpty());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TStrings * TTerminal::ProcessFeatures(TStrings * Features)
|
|
{
|
|
return ::ProcessFeatures(Features, SessionData->ProtocolFeatures.Trim());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TSecondaryTerminal::TSecondaryTerminal(
|
|
TTerminal * MainTerminal, TSessionData * ASessionData, TConfiguration * Configuration,
|
|
const UnicodeString & Name, TActionLog * ActionLog) :
|
|
TTerminal(ASessionData, Configuration, ActionLog),
|
|
FMainTerminal(MainTerminal)
|
|
{
|
|
Log->SetParent(FMainTerminal->Log, Name);
|
|
SessionData->NonPersistent();
|
|
DebugAssert(FMainTerminal != NULL);
|
|
FMainTerminal->FSecondaryTerminals++;
|
|
if (SessionData->TunnelLocalPortNumber != 0)
|
|
{
|
|
SessionData->TunnelLocalPortNumber = SessionData->TunnelLocalPortNumber + FMainTerminal->FSecondaryTerminals;
|
|
}
|
|
if (!FMainTerminal->UserName.IsEmpty())
|
|
{
|
|
SessionData->UserName = FMainTerminal->UserName;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TSecondaryTerminal::UpdateFromMain()
|
|
{
|
|
if ((FFileSystem != NULL) && (FMainTerminal->FFileSystem != NULL))
|
|
{
|
|
FFileSystem->UpdateFromMain(FMainTerminal->FFileSystem);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TSecondaryTerminal::DirectoryLoaded(TRemoteFileList * FileList)
|
|
{
|
|
FMainTerminal->DirectoryLoaded(FileList);
|
|
DebugAssert(FileList != NULL);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TSecondaryTerminal::DirectoryModified(const UnicodeString Path,
|
|
bool SubDirs)
|
|
{
|
|
// clear cache of main terminal
|
|
FMainTerminal->DirectoryModified(Path, SubDirs);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TSecondaryTerminal::GetPrimaryTerminal()
|
|
{
|
|
return FMainTerminal;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTerminalList::TTerminalList(TConfiguration * AConfiguration) :
|
|
TObjectList()
|
|
{
|
|
DebugAssert(AConfiguration);
|
|
FConfiguration = AConfiguration;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TTerminalList::~TTerminalList()
|
|
{
|
|
DebugAssert(Count == 0);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminalList::CreateTerminal(TSessionData * Data)
|
|
{
|
|
return new TTerminal(Data, FConfiguration);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminalList::NewTerminal(TSessionData * Data)
|
|
{
|
|
TTerminal * Terminal = CreateTerminal(Data);
|
|
Add(Terminal);
|
|
return Terminal;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalList::FreeTerminal(TTerminal * Terminal)
|
|
{
|
|
DebugAssert(IndexOf(Terminal) >= 0);
|
|
Remove(Terminal);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalList::FreeAndNullTerminal(TTerminal * & Terminal)
|
|
{
|
|
TTerminal * T = Terminal;
|
|
Terminal = NULL;
|
|
FreeTerminal(T);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TTerminal * __fastcall TTerminalList::GetTerminal(int Index)
|
|
{
|
|
return dynamic_cast<TTerminal *>(Items[Index]);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TTerminalList::RecryptPasswords()
|
|
{
|
|
for (int Index = 0; Index < Count; Index++)
|
|
{
|
|
Terminals[Index]->RecryptPasswords();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
TLocalFileHandle::TLocalFileHandle()
|
|
{
|
|
Handle = 0;
|
|
Attrs = 0;
|
|
MTime = 0;
|
|
ATime = 0;
|
|
Size = 0;
|
|
Directory = false;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TLocalFileHandle::~TLocalFileHandle()
|
|
{
|
|
Release();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TLocalFileHandle::Release()
|
|
{
|
|
if (Handle != 0)
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TLocalFileHandle::Dismiss()
|
|
{
|
|
if (DebugAlwaysTrue(Handle != 0))
|
|
{
|
|
Handle = 0;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void TLocalFileHandle::Close()
|
|
{
|
|
if (DebugAlwaysTrue(Handle != 0))
|
|
{
|
|
CloseHandle(Handle);
|
|
Dismiss();
|
|
}
|
|
}
|