1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00

Issue 2232 – Automatically reconnect inactive sessions

https://winscp.net/tracker/2232

Source commit: 083cfa045a8291cdfcc1fe8ce1e510a7d7619e7e
This commit is contained in:
Martin Prikryl
2025-04-08 15:07:35 +00:00
parent b9d83e87fa
commit bf7b9448eb
18 changed files with 710 additions and 268 deletions

View File

@@ -429,12 +429,14 @@ __fastcall EOSExtException::EOSExtException(UnicodeString Msg, int LastError) :
//---------------------------------------------------------------------------
__fastcall EFatal::EFatal(Exception* E, UnicodeString Msg, UnicodeString HelpKeyword) :
ExtException(Msg, E, HelpKeyword),
FReopenQueried(false)
ReopenQueried(false),
InactiveTerminationMessage(false)
{
EFatal * F = dynamic_cast<EFatal *>(E);
if (F != NULL)
{
FReopenQueried = F->ReopenQueried;
ReopenQueried = F->ReopenQueried;
InactiveTerminationMessage = F->InactiveTerminationMessage;
}
}
//---------------------------------------------------------------------------

View File

@@ -160,13 +160,11 @@ public:
// fatal errors are always copied, new message is only appended
__fastcall EFatal(Exception* E, UnicodeString Msg, UnicodeString HelpKeyword = L"");
__property bool ReopenQueried = { read = FReopenQueried, write = FReopenQueried };
bool ReopenQueried;
bool InactiveTerminationMessage;
virtual ExtException * __fastcall Clone();
virtual void __fastcall Rethrow();
private:
bool FReopenQueried;
};
//---------------------------------------------------------------------------
#define DERIVE_FATAL_EXCEPTION(NAME, BASE) \

View File

@@ -2566,6 +2566,12 @@ __fastcall TTerminalThread::~TTerminalThread()
FTerminal->OnReadDirectoryProgress = FOnReadDirectoryProgress;
FTerminal->OnInitializeLog = FOnInitializeLog;
while (!FNonInteractiveInformation.empty())
{
delete FNonInteractiveInformation.front();
FNonInteractiveInformation.pop_front();
}
delete FSection;
if (FAbandoned)
{
@@ -2605,6 +2611,63 @@ void __fastcall TTerminalThread::Idle()
}
}
//---------------------------------------------------------------------------
void TTerminalThread::StartTerminalReopenNonInteractive()
{
FNonInteractive = true;
StartAction(TerminalReopenEvent);
}
//---------------------------------------------------------------------------
TTerminalReopenResult TTerminalThread::IsTerminalReopenComplete()
{
DebugAssert(!FCancel);
DebugAssert(!FAbandoned);
DebugAssert(!FCancelled);
TTerminalReopenResult Result;
if (WaitForActionEvent(0))
{
if (FNeedsInteraction)
{
Result = trrNeedsInteraction;
}
else if (FException != NULL)
{
Result = trrFailed;
}
else
{
Result = trrSucceeded;
}
}
else
{
Result = trrPending;
}
if (Result != trrPending)
{
DiscardException();
}
DebugAssert(FUserAction == NULL);
return Result;
}
//---------------------------------------------------------------------------
void TTerminalThread::ContinueTerminalReopenInteractive()
{
FNonInteractive = false;
while (!FNonInteractiveInformation.empty())
{
std::unique_ptr<TInformationUserAction> Action(FNonInteractiveInformation.front());
FNonInteractiveInformation.pop_front();
Action->Execute(NULL);
}
CompleteAction();
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalOpen()
{
RunAction(TerminalOpenEvent);
@@ -2620,72 +2683,103 @@ void TTerminalThread::DiscardException()
SAFE_DESTROY(FException);
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::RunAction(TNotifyEvent Action)
void TTerminalThread::StartAction(TNotifyEvent Action)
{
DebugAssert(FAction == NULL);
DebugAssert(FException == NULL);
DebugAssert(FIdleException == NULL);
DebugAssert(FOnIdle != NULL);
FCancelled = false;
FAction = Action;
FNeedsInteraction = false;
TriggerEvent();
}
//---------------------------------------------------------------------------
bool TTerminalThread::WaitForActionEvent(unsigned int Wait)
{
switch (WaitForSingleObject(FActionEvent, Wait))
{
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
default:
throw Exception(L"Error waiting for background session task to complete");
}
}
//---------------------------------------------------------------------------
bool TTerminalThread::Abandoned()
{
bool Result = false;
if (AllowAbandon && FCancel && (Now() >= FCancelAfter))
{
TGuard Guard(FSection);
if (WaitForSingleObject(FActionEvent, 0) != WAIT_OBJECT_0)
{
FAbandoned = true;
FCancelled = true;
Result = true;
}
}
return Result;
}
//---------------------------------------------------------------------------
void TTerminalThread::RunAction(TNotifyEvent Action)
{
FNonInteractive = false;
StartAction(Action);
CompleteAction();
}
//---------------------------------------------------------------------------
void TTerminalThread::CompleteAction()
{
DebugAssert(FOnIdle != NULL);
try
{
try
{
TriggerEvent();
bool Done = false;
const unsigned int MaxWait = 50;
unsigned int Wait = MaxWait;
do
{
switch (WaitForSingleObject(FActionEvent, Wait))
if (WaitForActionEvent(Wait))
{
case WAIT_OBJECT_0:
Done = true;
break;
case WAIT_TIMEOUT:
if (FUserAction != NULL)
Done = true;
}
else
{
if (FUserAction != NULL)
{
try
{
try
{
FUserAction->Execute(NULL);
}
catch (Exception & E)
{
SaveException(E, FException);
}
FUserAction = NULL;
TriggerEvent();
Wait = 0;
FUserAction->Execute(NULL);
}
else
catch (Exception & E)
{
if (FOnIdle != NULL)
{
FOnIdle(NULL);
}
Wait = std::min(Wait + 10, MaxWait);
SaveException(E, FException);
}
break;
default:
throw Exception(L"Error waiting for background session task to complete");
FUserAction = NULL;
TriggerEvent();
Wait = 0;
}
else
{
if (FOnIdle != NULL)
{
FOnIdle(NULL);
}
Wait = std::min(Wait + 10, MaxWait);
}
}
if (AllowAbandon && !Done && FCancel && (Now() >= FCancelAfter))
if (!Done && Abandoned())
{
TGuard Guard(FSection);
if (WaitForSingleObject(FActionEvent, 0) != WAIT_OBJECT_0)
{
FAbandoned = true;
FCancelled = true;
FatalAbort();
}
FatalAbort();
}
}
while (!Done);
@@ -2792,6 +2886,7 @@ void __fastcall TTerminalThread::FatalAbort()
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::CheckCancel()
{
// Should we really fatally abort when e.g. just cancelling password prompt?
if (FCancel && !FCancelled)
{
FCancelled = true;
@@ -2801,6 +2896,7 @@ void __fastcall TTerminalThread::CheckCancel()
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::WaitForUserAction(TUserAction * UserAction)
{
DebugAssert(!FNonInteractive);
DWORD Thread = GetCurrentThreadId();
// we can get called from the main thread from within Idle,
// should be only to call HandleExtendedException
@@ -2898,13 +2994,25 @@ void __fastcall TTerminalThread::WaitForUserAction(TUserAction * UserAction)
void __fastcall TTerminalThread::TerminalInformation(
TTerminal * Terminal, const UnicodeString & Str, int Phase, const UnicodeString & Additional)
{
TInformationUserAction Action(FOnInformation);
Action.Terminal = Terminal;
Action.Str = Str;
Action.Phase = Phase;
Action.Additional = Additional;
WaitForUserAction(&Action);
std::unique_ptr<TInformationUserAction> Action(new TInformationUserAction(FOnInformation));
Action->Terminal = Terminal;
Action->Str = Str;
Action->Phase = Phase;
Action->Additional = Additional;
if (!FNonInteractive)
{
WaitForUserAction(Action.get());
}
else
{
FNonInteractiveInformation.push_back(Action.release());
}
}
//---------------------------------------------------------------------------
void TTerminalThread::NeedsInteraction()
{
FNeedsInteraction = true;
Cancel();
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalQueryUser(TObject * Sender,
@@ -2913,36 +3021,42 @@ void __fastcall TTerminalThread::TerminalQueryUser(TObject * Sender,
{
DebugUsedParam(Arg);
DebugAssert(Arg == NULL);
if (FNonInteractive)
{
NeedsInteraction();
Answer = AbortAnswer(Answers);
}
else
{
// note about TQueryParams::TimerEvent
// So far there is only one use for this, the TSecureShell::SendBuffer,
// which should be thread-safe, as long as the terminal thread,
// is stopped waiting for OnQueryUser to finish.
// note about TQueryParams::TimerEvent
// So far there is only one use for this, the TSecureShell::SendBuffer,
// which should be thread-safe, as long as the terminal thread,
// is stopped waiting for OnQueryUser to finish.
// note about TQueryButtonAlias::OnClick
// So far there is only one use for this, the TClipboardHandler,
// which is thread-safe.
// note about TQueryButtonAlias::OnClick
// So far there is only one use for this, the TClipboardHandler,
// which is thread-safe.
TQueryUserAction Action(FOnQueryUser);
Action.Sender = Sender;
Action.Query = Query;
Action.MoreMessages = MoreMessages;
Action.Answers = Answers;
Action.Params = Params;
Action.Answer = Answer;
Action.Type = Type;
TQueryUserAction Action(FOnQueryUser);
Action.Sender = Sender;
Action.Query = Query;
Action.MoreMessages = MoreMessages;
Action.Answers = Answers;
Action.Params = Params;
Action.Answer = Answer;
Action.Type = Type;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
Answer = Action.Answer;
Answer = Action.Answer;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalInitializeLog(TObject * Sender)
{
if (FOnInitializeLog != NULL)
// never used, so not tested either
if (DebugAlwaysFalse(FOnInitializeLog != NULL) && DebugAlwaysTrue(!FNonInteractive))
{
// never used, so not tested either
DebugFail();
TNotifyAction Action(FOnInitializeLog);
Action.Sender = Sender;
@@ -2955,91 +3069,121 @@ void __fastcall TTerminalThread::TerminalPromptUser(TTerminal * Terminal,
TStrings * Results, bool & Result, void * Arg)
{
DebugUsedParam(Arg);
DebugAssert(Arg == NULL);
if (FNonInteractive)
{
NeedsInteraction();
Result = false;
}
else
{
DebugAssert(Arg == NULL);
TPromptUserAction Action(FOnPromptUser);
Action.Terminal = Terminal;
Action.Kind = Kind;
Action.Name = Name;
Action.Instructions = Instructions;
Action.Prompts = Prompts;
Action.Results->AddStrings(Results);
TPromptUserAction Action(FOnPromptUser);
Action.Terminal = Terminal;
Action.Kind = Kind;
Action.Name = Name;
Action.Instructions = Instructions;
Action.Prompts = Prompts;
Action.Results->AddStrings(Results);
WaitForUserAction(&Action);
WaitForUserAction(&Action);
Results->Clear();
Results->AddStrings(Action.Results);
Result = Action.Result;
Results->Clear();
Results->AddStrings(Action.Results);
Result = Action.Result;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalShowExtendedException(
TTerminal * Terminal, Exception * E, void * Arg)
{
DebugUsedParam(Arg);
DebugAssert(Arg == NULL);
if (FNonInteractive)
{
NeedsInteraction();
}
else
{
DebugAssert(Arg == NULL);
TShowExtendedExceptionAction Action(FOnShowExtendedException);
Action.Terminal = Terminal;
Action.E = E;
TShowExtendedExceptionAction Action(FOnShowExtendedException);
Action.Terminal = Terminal;
Action.E = E;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalDisplayBanner(TTerminal * Terminal,
UnicodeString SessionName, const UnicodeString & Banner,
bool & NeverShowAgain, int Options, unsigned int & Params)
{
TDisplayBannerAction Action(FOnDisplayBanner);
Action.Terminal = Terminal;
Action.SessionName = SessionName;
Action.Banner = Banner;
Action.NeverShowAgain = NeverShowAgain;
Action.Options = Options;
Action.Params = Params;
if (!FNonInteractive)
{
TDisplayBannerAction Action(FOnDisplayBanner);
Action.Terminal = Terminal;
Action.SessionName = SessionName;
Action.Banner = Banner;
Action.NeverShowAgain = NeverShowAgain;
Action.Options = Options;
Action.Params = Params;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
NeverShowAgain = Action.NeverShowAgain;
Params = Action.Params;
NeverShowAgain = Action.NeverShowAgain;
Params = Action.Params;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalChangeDirectory(TObject * Sender)
{
TNotifyAction Action(FOnChangeDirectory);
Action.Sender = Sender;
if (!FNonInteractive)
{
TNotifyAction Action(FOnChangeDirectory);
Action.Sender = Sender;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalReadDirectory(TObject * Sender, Boolean ReloadOnly)
{
TReadDirectoryAction Action(FOnReadDirectory);
Action.Sender = Sender;
Action.ReloadOnly = ReloadOnly;
if (!FNonInteractive)
{
TReadDirectoryAction Action(FOnReadDirectory);
Action.Sender = Sender;
Action.ReloadOnly = ReloadOnly;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalStartReadDirectory(TObject * Sender)
{
TNotifyAction Action(FOnStartReadDirectory);
Action.Sender = Sender;
if (!FNonInteractive)
{
TNotifyAction Action(FOnStartReadDirectory);
Action.Sender = Sender;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalThread::TerminalReadDirectoryProgress(
TObject * Sender, int Progress, int ResolvedLinks, bool & Cancel)
{
TReadDirectoryProgressAction Action(FOnReadDirectoryProgress);
Action.Sender = Sender;
Action.Progress = Progress;
Action.ResolvedLinks = ResolvedLinks;
Action.Cancel = Cancel;
if (!FNonInteractive)
{
TReadDirectoryProgressAction Action(FOnReadDirectoryProgress);
Action.Sender = Sender;
Action.Progress = Progress;
Action.ResolvedLinks = ResolvedLinks;
Action.Cancel = Cancel;
WaitForUserAction(&Action);
WaitForUserAction(&Action);
Cancel = Action.Cancel;
Cancel = Action.Cancel;
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminalThread::Release()

View File

@@ -404,6 +404,8 @@ private:
};
//---------------------------------------------------------------------------
class TUserAction;
class TInformationUserAction;
enum TTerminalReopenResult { trrPending, trrSucceeded, trrFailed, trrNeedsInteraction };
class TTerminalThread : public TSignalThread
{
public:
@@ -412,6 +414,10 @@ public:
void __fastcall TerminalOpen();
void __fastcall TerminalReopen();
void StartTerminalReopenNonInteractive();
TTerminalReopenResult IsTerminalReopenComplete();
void ContinueTerminalReopenInteractive();
bool Abandoned();
void __fastcall Cancel();
bool __fastcall Release();
@@ -454,12 +460,17 @@ private:
bool FCancelled;
bool FPendingIdle;
bool FAllowAbandon;
bool FNonInteractive;
bool FNeedsInteraction;
typedef std::list<TInformationUserAction *> TInformationList;
TInformationList FNonInteractiveInformation;
DWORD FMainThread;
TCriticalSection * FSection;
void __fastcall WaitForUserAction(TUserAction * UserAction);
void __fastcall RunAction(TNotifyEvent Action);
void StartAction(TNotifyEvent Action);
void RunAction(TNotifyEvent Action);
static void __fastcall SaveException(Exception & E, Exception *& Exception);
static void __fastcall Rethrow(Exception *& Exception);
@@ -488,6 +499,9 @@ private:
void __fastcall TerminalReadDirectoryProgress(TObject * Sender, int Progress, int ResolvedLinks, bool & Cancel);
void __fastcall TerminalInitializeLog(TObject * Sender);
void DiscardException();
bool WaitForActionEvent(unsigned int Wait);
void CompleteAction();
void NeedsInteraction();
};
//---------------------------------------------------------------------------
enum TQueueFileState { qfsQueued = 0, qfsProcessed = 1 };

View File

@@ -3973,17 +3973,40 @@ void TCustomScpExplorerForm::EditedFileUploaded(TTerminal * ATerminal, HANDLE Up
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
const UnicodeString & FileName, TEditedFileData * Data, HANDLE UploadCompleteEvent, bool & Retry)
const UnicodeString & FileName, const TDateTime & Timestamp, TEditedFileData * Data,
HANDLE UploadCompleteEvent, bool & Retry)
{
TTerminalManager * Manager = TTerminalManager::Instance();
bool IsInactive = !IsActiveTerminal(Data->Terminal);
if ((Manager->ActiveTerminal == Data->Terminal) &&
Manager->ScheduleTerminalReconnnect(Data->Terminal))
{
AppLog(L"Scheduled session reconnect, will retry the upload later");
Retry = true;
}
else if (!IsActiveTerminal(Data->Terminal))
else if (Manager->IsReconnectingTerminal(Data->Terminal))
{
AppLog(L"Session is being reconnected, will retry the upload later");
Retry = true;
}
else if (IsInactive &&
GUIConfiguration->SessionReopenAutoInactive &&
(Data->Terminal->NextInactiveReconnect != TDateTime()))
{
if (Data->Terminal->LastInactiveSave != Timestamp)
{
AppLog(L"Inactive session reconnect is pending, rescheduling it for as soon as possible to retry the upload");
Data->Terminal->NextInactiveReconnect = Now();
Data->Terminal->LastInactiveSave = Timestamp;
}
else
{
AppLog(L"Inactive session reconnect is pending, will retry the upload later");
}
Retry = true;
}
else if (IsInactive)
{
if (!NonVisualDataModule->Busy)
{
@@ -4018,7 +4041,16 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
UnicodeString Message =
MainInstructions(
FMTLOAD(EDIT_SESSION_RECONNECT, (Data->SessionName, FileNameOnly)));
if (MessageDialog(Message, qtConfirmation, qaOK | qaCancel) == qaOK)
TMessageParams Params(FLAGMASK(!GUIConfiguration->SessionReopenAutoInactive, mpNeverAskAgainCheck));
Params.NeverAskAgainTitle = LoadStr(ALWAYS_RECONNECT);
unsigned int Answer = MessageDialog(Message, qtConfirmation, qaOK | qaCancel, HELP_NONE, &Params);
if (Answer == qaNeverAskAgain)
{
GUIConfiguration->SessionReopenAutoInactive = true;
Answer = qaOK;
}
if (Answer == qaOK)
{
if (Terminal->Disconnected)
{
@@ -7989,25 +8021,23 @@ void __fastcall TCustomScpExplorerForm::SessionReady()
UpdateSessionTab(SessionsPageControl->Pages[ActiveSessionIndex]);
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::InactiveTerminalException(
TTerminal * Terminal, Exception * E)
void TCustomScpExplorerForm::InactiveTerminalNotify(
TManagedTerminal * Terminal, const UnicodeString & Message, TQueryType Type, Exception * E)
{
Notify(Terminal, L"", qtError, false, NULL, NULL, E);
Notify(Terminal, Message, Type, false, NULL, NULL, E);
if (!Terminal->Active)
int Index = TTerminalManager::Instance()->IndexOf(Terminal);
if (DebugAlwaysTrue((Index >= 0) && (Index < SessionsPageControl->PageCount)))
{
int Index = TTerminalManager::Instance()->IndexOf(Terminal);
if (DebugAlwaysTrue((Index >= 0) && (Index < SessionsPageControl->PageCount)))
{
UpdateSessionTab(SessionsPageControl->Pages[Index]);
}
UpdateSessionTab(SessionsPageControl->Pages[Index]);
}
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::Notify(TTerminal * Terminal,
UnicodeString Message, TQueryType Type,
void TCustomScpExplorerForm::Notify(
TManagedTerminal * Terminal, const UnicodeString & AMessage, TQueryType Type,
bool Important, TNotifyEvent OnClick, TObject * UserData, Exception * E)
{
UnicodeString Message = AMessage;
if ((E == NULL) ||
ExceptionMessage(E, Message))
{
@@ -8020,9 +8050,7 @@ void __fastcall TCustomScpExplorerForm::Notify(TTerminal * Terminal,
UnicodeString NoteMessage(UnformatMessage(Message));
if (Terminal != NULL)
{
TManagedTerminal * Session = DebugNotNull(dynamic_cast<TManagedTerminal *>(Terminal));
NoteMessage = FORMAT(L"%s: %s",
(TTerminalManager::Instance()->GetSessionTitle(Session, true), NoteMessage));
NoteMessage = TTerminalManager::Instance()->FormatTerminalNoteMessage(Terminal, NoteMessage);
}
if (WinConfiguration->BalloonNotifications)
@@ -9573,7 +9601,7 @@ void __fastcall TCustomScpExplorerForm::UpdatesChecked()
if (!New && (Type != qtWarning))
{
AppLog(L"Posting non-critical updates note");
PostNote(UnformatMessage(Message), 0, UpdatesNoteClicked, NULL);
PostNote(UnformatMessage(Message), 0, UpdatesNoteClicked);
}
else
{
@@ -10228,6 +10256,14 @@ bool __fastcall TCustomScpExplorerForm::CancelNote(bool Force)
return Result;
}
//---------------------------------------------------------------------------
void TCustomScpExplorerForm::CancelNote(const UnicodeString & Note)
{
if (FNote == Note)
{
CancelNote(true);
}
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::NoteTimer(TObject * /*Sender*/)
{
DebugAssert(FNoteTimer->Enabled);
@@ -10261,9 +10297,10 @@ void __fastcall TCustomScpExplorerForm::AddNote(UnicodeString Note, bool UpdateN
}
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::PostNote(UnicodeString Note,
unsigned int Seconds, TNotifyEvent OnNoteClick, TObject * NoteData)
void TCustomScpExplorerForm::PostNote(
const UnicodeString & ANote, unsigned int Seconds, TNotifyEvent OnNoteClick, TObject * NoteData)
{
UnicodeString Note = ANote;
int P = Note.Pos(L"\n");
if (P > 0)
{
@@ -10289,7 +10326,7 @@ void __fastcall TCustomScpExplorerForm::PostNote(UnicodeString Note,
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::ReadDirectoryCancelled()
{
PostNote(LoadStr(DIRECTORY_READING_CANCELLED), 0, NULL, NULL);
PostNote(LoadStr(DIRECTORY_READING_CANCELLED));
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::SynchronizeBrowsingChanged()
@@ -10301,8 +10338,7 @@ void __fastcall TCustomScpExplorerForm::SynchronizeBrowsingChanged()
PostNote(FORMAT(LoadStrPart(SYNC_DIR_BROWSE_TOGGLE, 1),
(LoadStrPart(SYNC_DIR_BROWSE_TOGGLE,
(NonVisualDataModule->SynchronizeBrowsingAction2->Checked ? 2 : 3)))),
0, NULL, NULL);
(NonVisualDataModule->SynchronizeBrowsingAction2->Checked ? 2 : 3)))));
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::ToggleShowHiddenFiles()
@@ -10310,7 +10346,7 @@ void __fastcall TCustomScpExplorerForm::ToggleShowHiddenFiles()
bool NewShowHiddenFiles = !WinConfiguration->ShowHiddenFiles;
UnicodeString Note =
FORMAT(LoadStrPart(SHOW_HIDDEN_FILES_TOGGLE, 1), (LoadStrPart(SHOW_HIDDEN_FILES_TOGGLE, (NewShowHiddenFiles ? 2 : 3))));
PostNote(Note, 0, NULL, NULL);
PostNote(Note);
GetComponent(fcStatusBar)->Repaint(); // toggling ShowHiddenFiles takes time, force repaint beforehand
WinConfiguration->ShowHiddenFiles = NewShowHiddenFiles;
}
@@ -10325,7 +10361,7 @@ void __fastcall TCustomScpExplorerForm::ToggleAutoReadDirectoryAfterOp()
Configuration->AutoReadDirectoryAfterOp = !Configuration->AutoReadDirectoryAfterOp;
PostNote(FORMAT(LoadStrPart(AUTO_READ_DIRECTORY_TOGGLE, 1),
(LoadStrPart(AUTO_READ_DIRECTORY_TOGGLE,
(Configuration->AutoReadDirectoryAfterOp ? 2 : 3)))), 0, NULL, NULL);
(Configuration->AutoReadDirectoryAfterOp ? 2 : 3)))));
}
//---------------------------------------------------------------------------
void __fastcall TCustomScpExplorerForm::StatusBarPanelDblClick(
@@ -11985,7 +12021,7 @@ void __fastcall TCustomScpExplorerForm::CloseApp()
//---------------------------------------------------------------------------
bool __fastcall TCustomScpExplorerForm::IsActiveTerminal(TTerminal * Terminal)
{
return (Terminal != NULL) && Terminal->Active;
return TTerminalManager::Instance()->IsActiveTerminal(Terminal);
}
//---------------------------------------------------------------------------
bool __fastcall TCustomScpExplorerForm::HasManagedSession()

View File

@@ -472,7 +472,8 @@ protected:
void __fastcall OperationComplete(const TDateTime & StartTime);
void EditedFileUploaded(TTerminal * ATerminal, HANDLE UploadCompleteEvent);
void __fastcall ExecutedFileChanged(
const UnicodeString & FileName, TEditedFileData * Data, HANDLE UploadCompleteEvent, bool & Retry);
const UnicodeString & FileName, const TDateTime & Timestamp, TEditedFileData * Data,
HANDLE UploadCompleteEvent, bool & Retry);
void __fastcall ExecutedFileReload(const UnicodeString & FileName, TEditedFileData * Data);
void __fastcall ExecutedFileEarlyClosed(const TEditedFileData * Data,
bool & KeepOpen);
@@ -627,8 +628,6 @@ protected:
void __fastcall SetSessionColor(TColor value);
void __fastcall NoteTimer(TObject * Sender);
void __fastcall AddNote(UnicodeString Note, bool UpdateNow = true);
void __fastcall PostNote(UnicodeString Note, unsigned int Seconds,
TNotifyEvent OnNoteClick, TObject * NoteData);
bool __fastcall CancelNote(bool Force);
void __fastcall UpdateNoteHints();
void __fastcall UpdatesChecked();
@@ -638,9 +637,6 @@ protected:
bool AllowNeverAskAgain);
void __fastcall UpdateTrayIcon();
void __fastcall TrayIconClick(TObject * Sender);
void __fastcall Notify(TTerminal * Terminal, UnicodeString Message,
TQueryType Type, bool Important = false, TNotifyEvent OnClick = NULL,
TObject * UserData = NULL, Exception * E = NULL);
virtual void __fastcall UpdateSessionData(TSessionData * Data);
virtual void __fastcall UpdateRemotePathComboBox(bool TextOnly);
virtual void __fastcall ToolbarItemResize(TTBXCustomDropDownItem * Item, int Width);
@@ -873,7 +869,8 @@ public:
void __fastcall OperationProgress(TFileOperationProgressType & ProgressData);
UnicodeString __fastcall GetProgressTitle();
void __fastcall ShowExtendedException(TTerminal * Terminal, Exception * E);
void __fastcall InactiveTerminalException(TTerminal * Terminal, Exception * E);
void InactiveTerminalNotify(
TManagedTerminal * Terminal, const UnicodeString & Message, TQueryType Type, Exception * E = NULL);
void __fastcall SessionReady();
void __fastcall QueueEvent(TManagedTerminal * Terminal, TTerminalQueue * Queue, TQueueEvent Event);
void __fastcall QueueEmptyNoteClicked(TObject * Sender);
@@ -937,6 +934,11 @@ public:
void PostThumbnailVisibleQueueQuery(int Index, const UnicodeString & FileName);
void PostThumbnailDrawRequest(int Index);
void ChangeDirViewStyle(TOperationSide Side, TDirViewStyle DirViewStyle);
void PostNote(const UnicodeString & Note, unsigned int Seconds = 0, TNotifyEvent OnNoteClick = NULL, TObject * NoteData = NULL);
void CancelNote(const UnicodeString & Note);
void Notify(
TManagedTerminal * Terminal, const UnicodeString & Message, TQueryType Type,
bool Important = false, TNotifyEvent OnClick = NULL, TObject * UserData = NULL, Exception * E = NULL);
__property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
__property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };

View File

@@ -427,9 +427,12 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
SessionReopenAutoCheck->Checked = (Configuration->SessionReopenAuto > 0);
SessionReopenAutoEdit->Value = (Configuration->SessionReopenAuto > 0 ?
(Configuration->SessionReopenAuto / MSecsPerSec) : 5);
SessionReopenAutoIdleCheck->Checked = (GUIConfiguration->SessionReopenAutoIdle > 0);
SessionReopenAutoIdleEdit->Value = (GUIConfiguration->SessionReopenAutoIdle > 0 ?
(GUIConfiguration->SessionReopenAutoIdle / MSecsPerSec) : 5);
SessionReopenAutoIdleCheck->Checked =
GUIConfiguration->SessionReopenAutoIdleOn && (GUIConfiguration->SessionReopenAutoIdle > 0);
SessionReopenAutoInactiveCheck->Checked = GUIConfiguration->SessionReopenAutoInactive;
int SessionReopenAutoIdle =
(GUIConfiguration->SessionReopenAutoIdle > 0 ? GUIConfiguration->SessionReopenAutoIdle : SessionReopenAutoIdleDefault);
SessionReopenAutoIdleEdit->Value = (SessionReopenAutoIdle / MSecsPerSec);
SessionReopenAutoStallCheck->Checked = (Configuration->SessionReopenAutoStall > 0);
SessionReopenAutoStallEdit->Value = (Configuration->SessionReopenAutoStall > 0 ?
(Configuration->SessionReopenAutoStall / MSecsPerSec) : SecsPerMin);
@@ -829,8 +832,9 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
Configuration->SessionReopenAuto =
(SessionReopenAutoCheck->Checked ? (SessionReopenAutoEdit->AsInteger * MSecsPerSec) : 0);
GUIConfiguration->SessionReopenAutoIdle =
(SessionReopenAutoIdleCheck->Checked ? (SessionReopenAutoIdleEdit->AsInteger * MSecsPerSec) : 0);
GUIConfiguration->SessionReopenAutoIdleOn = SessionReopenAutoIdleCheck->Checked;
GUIConfiguration->SessionReopenAutoInactive = SessionReopenAutoInactiveCheck->Checked;
GUIConfiguration->SessionReopenAutoIdle = (SessionReopenAutoIdleEdit->AsInteger * MSecsPerSec);
Configuration->SessionReopenAutoStall =
(SessionReopenAutoStallCheck->Checked ? (SessionReopenAutoStallEdit->AsInteger * MSecsPerSec) : 0);
Configuration->SessionReopenTimeout = (SessionReopenTimeoutEdit->AsInteger * MSecsPerSec);
@@ -1289,7 +1293,7 @@ void __fastcall TPreferencesDialog::UpdateControls()
EnableControl(SessionReopenAutoEdit, SessionReopenAutoCheck->Checked);
EnableControl(SessionReopenAutoLabel, SessionReopenAutoEdit->Enabled);
EnableControl(SessionReopenAutoSecLabel, SessionReopenAutoEdit->Enabled);
EnableControl(SessionReopenAutoIdleEdit, SessionReopenAutoIdleCheck->Checked);
EnableControl(SessionReopenAutoIdleEdit, SessionReopenAutoIdleCheck->Checked || SessionReopenAutoInactiveCheck->Checked);
EnableControl(SessionReopenAutoIdleLabel, SessionReopenAutoIdleEdit->Enabled);
EnableControl(SessionReopenAutoIdleSecLabel, SessionReopenAutoIdleEdit->Enabled);
EnableControl(SessionReopenAutoStallEdit, SessionReopenAutoStallCheck->Checked);

View File

@@ -2010,13 +2010,13 @@ object PreferencesDialog: TPreferencesDialog
Left = 8
Top = 132
Width = 445
Height = 211
Height = 234
Anchors = [akLeft, akTop, akRight]
Caption = 'Automatic reconnect'
TabOrder = 1
DesignSize = (
445
211)
234)
object SessionReopenAutoLabel: TLabel
Left = 27
Top = 48
@@ -2035,7 +2035,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenTimeoutLabel: TLabel
Left = 9
Top = 181
Top = 204
Width = 120
Height = 15
Caption = '&Keep reconnecting for:'
@@ -2043,7 +2043,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenTimeoutSecLabel: TLabel
Left = 286
Top = 181
Top = 204
Width = 43
Height = 15
Caption = 'seconds'
@@ -2051,7 +2051,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenAutoStallLabel: TLabel
Left = 27
Top = 152
Top = 175
Width = 86
Height = 15
Caption = 'Re&connect after:'
@@ -2059,7 +2059,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenAutoStallSecLabel: TLabel
Left = 286
Top = 152
Top = 175
Width = 43
Height = 15
Caption = 'seconds'
@@ -2067,7 +2067,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenAutoIdleLabel: TLabel
Left = 27
Top = 100
Top = 123
Width = 86
Height = 15
Caption = 'Reco&nnect after:'
@@ -2075,7 +2075,7 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenAutoIdleSecLabel: TLabel
Left = 286
Top = 100
Top = 123
Width = 43
Height = 15
Caption = 'seconds'
@@ -2116,30 +2116,30 @@ object PreferencesDialog: TPreferencesDialog
end
object SessionReopenTimeoutEdit: TUpDownEdit
Left = 183
Top = 178
Top = 201
Width = 97
Height = 23
Alignment = taRightJustify
Increment = 30.000000000000000000
MaxValue = 86400.000000000000000000
MaxLength = 5
TabOrder = 6
TabOrder = 7
OnGetValue = SessionReopenTimeoutEditGetValue
OnSetValue = SessionReopenTimeoutEditSetValue
end
object SessionReopenAutoStallCheck: TCheckBox
Left = 11
Top = 126
Top = 149
Width = 425
Height = 17
Anchors = [akLeft, akTop, akRight]
Caption = 'Automatically reconnect session, if it &stalls'
TabOrder = 4
TabOrder = 5
OnClick = ControlChange
end
object SessionReopenAutoStallEdit: TUpDownEdit
Left = 183
Top = 149
Top = 172
Width = 97
Height = 23
Alignment = taRightJustify
@@ -2148,11 +2148,11 @@ object PreferencesDialog: TPreferencesDialog
MinValue = 1.000000000000000000
Value = 5.000000000000000000
MaxLength = 3
TabOrder = 5
TabOrder = 6
end
object SessionReopenAutoIdleEdit: TUpDownEdit
Left = 183
Top = 97
Top = 120
Width = 97
Height = 23
Alignment = taRightJustify
@@ -2161,7 +2161,17 @@ object PreferencesDialog: TPreferencesDialog
MinValue = 1.000000000000000000
Value = 5.000000000000000000
MaxLength = 3
TabOrder = 4
end
object SessionReopenAutoInactiveCheck: TCheckBox
Left = 11
Top = 97
Width = 425
Height = 17
Anchors = [akLeft, akTop, akRight]
Caption = 'Automatically reconnect session, if it breaks &while inactive'
TabOrder = 3
OnClick = ControlChange
end
end
end

View File

@@ -359,6 +359,7 @@ __published:
TComboEdit *SearchEdit;
TTabSheet *SearchSheet;
TGroupBox *SearchGroup;
TCheckBox *SessionReopenAutoInactiveCheck;
void __fastcall FormShow(TObject *Sender);
void __fastcall ControlChange(TObject *Sender);
void __fastcall EditorFontButtonClick(TObject *Sender);

View File

@@ -164,6 +164,7 @@
#define CLOSE_WORKSPACE 1372
#define TOO_MANY_SESSIONS 1373
#define EDIT_CHANGED_EXTERNALLY 1374
#define ALWAYS_RECONNECT 1375
#define WIN_INFORMATION_STRINGS 1400
#define COMPARE_NO_DIFFERENCES 1402
@@ -316,6 +317,9 @@
#define PATTERNS_HINT_K 7000
#define PATTERNS_HINT_BACKSLASH 7001
#define USAGE_COPYID 7002
#define SESSION_RECONNECTING 7003
#define SESSION_RECONNECTED 7004
#define SESSION_NEED_INTERACTION 7005
#define WIN_FORMS_STRINGS 1600
#define COPY_FILE 1605

View File

@@ -168,6 +168,7 @@ BEGIN
CLOSE_WORKSPACE, "Close application without saving a workspace?"
TOO_MANY_SESSIONS, "**Do you really want to open another tab?**\nYou have %d tabs opened already. Please consider closing some tabs first to free resources of your computer."
EDIT_CHANGED_EXTERNALLY, "The remote file has been changed while you were editing it. Do you want to overwrite it anyway?"
ALWAYS_RECONNECT, "&Always reconnect automatically"
WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
COMPARE_NO_DIFFERENCES, "No differences found."
@@ -321,6 +322,9 @@ BEGIN
PATTERNS_HINT_K, "!K expands to current session private key path"
PATTERNS_HINT_BACKSLASH, "!\\ expands to current local path"
USAGE_COPYID, "Installs your public key in a server's authorized_keys."
SESSION_RECONNECTING, "Trying to reconnect..."
SESSION_RECONNECTED, "Reconnected."
SESSION_NEED_INTERACTION, "Session needs interaction to reconnect."
WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
COPY_FILE, "%s file '%s' to %s:"

View File

@@ -320,7 +320,7 @@ void __fastcall TEditorManager::Check()
if (Index >= 0)
{
UploadComplete(Index);
UploadComplete(Index, false);
}
}
while ((Index >= 0) && (FUploadCompleteEvents.size() > 0));
@@ -405,7 +405,7 @@ void __fastcall TEditorManager::AddFile(TFileData & FileData, TEditedFileData *
Data.release(); // ownership passed
}
//---------------------------------------------------------------------------
void __fastcall TEditorManager::UploadComplete(int Index)
void TEditorManager::UploadComplete(int Index, bool Retry)
{
TFileData * FileData = &FFiles[Index];
@@ -427,7 +427,9 @@ void __fastcall TEditorManager::UploadComplete(int Index)
FileData->Reupload = false;
CheckFileChange(Index, true);
}
else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
// so far used only to signal to the internal editor that saving was complete,
// so we do signal once an actual upload is done only
else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL) && !Retry)
{
FOnFileUploadComplete(FileData->Token);
}
@@ -583,11 +585,11 @@ void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
DebugAssert(OnFileChange != NULL);
bool Retry = false;
AppLogFmt(L"Uploading opened/edited file \"%s\".", (FileData->FileName));
OnFileChange(FileData->FileName, FileData->Data, FileData->UploadCompleteEvent, Retry);
OnFileChange(FileData->FileName, FileData->Timestamp, FileData->Data, FileData->UploadCompleteEvent, Retry);
if (Retry)
{
AppLogFmt(L"Will retry uploading opened/edited file \"%s\".", (FileData->FileName));
UploadComplete(Index);
UploadComplete(Index, true);
FileData->Timestamp = PrevTimestamp;
}
}
@@ -597,7 +599,7 @@ void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
// upload failed (was not even started)
if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
{
UploadComplete(Index);
UploadComplete(Index, false);
}
throw;
}

View File

@@ -24,7 +24,7 @@ struct TEditedFileData
};
//---------------------------------------------------------------------------
typedef void __fastcall (__closure * TEditedFileChangedEvent)
(const UnicodeString & FileName, TEditedFileData * Data, HANDLE CompleteEvent, bool & Retry);
(const UnicodeString & FileName, const TDateTime & Timestamp, TEditedFileData * Data, HANDLE CompleteEvent, bool & Retry);
typedef void __fastcall (__closure * TEditedFileReloadEvent)
(const UnicodeString & FileName, TEditedFileData * Data);
typedef void __fastcall (__closure * TEditedFileEarlyClosedEvent)
@@ -96,7 +96,7 @@ private:
TCriticalSection * FSection;
void __fastcall AddFile(TFileData & FileData, TEditedFileData * Data);
void __fastcall UploadComplete(int Index);
void UploadComplete(int Index, bool Retry);
bool __fastcall CloseFile(int Index, bool IgnoreErrors, bool Delete);
void __fastcall CloseProcess(int Index);
bool __fastcall EarlyClose(int Index);

View File

@@ -21,6 +21,7 @@ const int ccCopyResults = ccUser << 2;
const int ccRemoteFiles = ccUser << 3;
const int ccShowResultsInMsgBox = ccUser << 4;
const int ccSet = 0x80000000;
const int SessionReopenAutoIdleDefault = 9000;
//---------------------------------------------------------------------------
static const unsigned int AdditionalLanguageMask = 0xFFFFFF00;
static const UnicodeString AdditionalLanguagePrefix(L"XX");
@@ -578,7 +579,9 @@ void __fastcall TGUIConfiguration::Default()
FCopyParamCurrent = L"";
FKeepUpToDateChangeDelay = 500;
FChecksumAlg = L"sha1";
FSessionReopenAutoIdle = 9000;
FSessionReopenAutoIdle = SessionReopenAutoIdleDefault;
FSessionReopenAutoIdleOn = true;
FSessionReopenAutoInactive = false;
FNewDirectoryProperties.Default();
FNewDirectoryProperties.Rights = TRights::rfDefault | TRights::rfExec;
@@ -655,6 +658,8 @@ void __fastcall TGUIConfiguration::UpdateStaticUsage()
KEY(Integer, KeepUpToDateChangeDelay); \
KEY(String, ChecksumAlg); \
KEY(Integer, SessionReopenAutoIdle); \
KEY(Bool, SessionReopenAutoIdleOn); \
KEY(Bool, SessionReopenAutoInactive); \
); \
//---------------------------------------------------------------------------
bool __fastcall TGUIConfiguration::DoSaveCopyParam(THierarchicalStorage * Storage, const TCopyParamType * CopyParam, const TCopyParamType * Defaults)

View File

@@ -15,6 +15,7 @@ extern const int ccCopyResults;
extern const int ccSet;
extern const int ccRemoteFiles;
extern const int ccShowResultsInMsgBox;
extern const int SessionReopenAutoIdleDefault;
//---------------------------------------------------------------------------
const int soRecurse = 0x01;
const int soSynchronize = 0x02;
@@ -187,6 +188,8 @@ private:
int FKeepUpToDateChangeDelay;
UnicodeString FChecksumAlg;
int FSessionReopenAutoIdle;
bool FSessionReopenAutoIdleOn;
bool FSessionReopenAutoInactive;
LCID FAppliedLocale;
// Corresponds to FAppliedLocale
UnicodeString FLocaleModuleName;
@@ -288,6 +291,8 @@ public:
__property int KeepUpToDateChangeDelay = { read = FKeepUpToDateChangeDelay, write = FKeepUpToDateChangeDelay };
__property UnicodeString ChecksumAlg = { read = FChecksumAlg, write = FChecksumAlg };
__property int SessionReopenAutoIdle = { read = FSessionReopenAutoIdle, write = FSessionReopenAutoIdle };
__property bool SessionReopenAutoIdleOn = { read = FSessionReopenAutoIdleOn, write = FSessionReopenAutoIdleOn };
__property bool SessionReopenAutoInactive = { read = FSessionReopenAutoInactive, write = FSessionReopenAutoInactive };
__property bool CanApplyLocaleImmediately = { read = GetCanApplyLocaleImmediately };
__property LCID AppliedLocale = { read = FAppliedLocale };
};

View File

@@ -22,6 +22,7 @@
#include <HelpWin.h>
#include <System.IOUtils.hpp>
#include <StrUtils.hpp>
#include <DateUtils.hpp>
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
@@ -158,6 +159,7 @@ __fastcall TTerminalManager::TTerminalManager() :
FKeepAuthenticateForm = false;
FUpdating = 0;
FOpeningTerminal = NULL;
FReconnectingInactiveTerminal = NULL;
FApplicationsEvents.reset(new TApplicationEvents(Application));
FApplicationsEvents->OnException = ApplicationException;
@@ -178,7 +180,6 @@ __fastcall TTerminalManager::TTerminalManager() :
FSessionList = new TStringList();
FQueues = new TList();
FTerminationMessages = new TStringList();
std::unique_ptr<TSessionData> DummyData(new TSessionData(L""));
FLocalTerminal = CreateTerminal(DummyData.get());
SetupTerminal(FLocalTerminal);
@@ -200,7 +201,6 @@ __fastcall TTerminalManager::~TTerminalManager()
delete FLocalTerminal;
delete FQueues;
delete FTerminationMessages;
delete FSessionList;
CloseAutheticateForm();
delete FQueueSection;
@@ -272,7 +272,6 @@ TManagedTerminal * __fastcall TTerminalManager::DoNewSession(TSessionData * Data
try
{
FQueues->Add(NewQueue(Session));
FTerminationMessages->Add(L"");
SetupTerminal(Session);
}
@@ -377,49 +376,85 @@ void __fastcall TTerminalManager::FreeActiveTerminal()
}
}
//---------------------------------------------------------------------------
TTerminalThread * TTerminalManager::CreateTerminalThread(TTerminal * Terminal)
{
TTerminalThread * TerminalThread = new TTerminalThread(Terminal);
TerminalThread->OnIdle = MakeMethod<TNotifyEvent>(NULL, TerminalThreadIdle);
return TerminalThread;
}
//---------------------------------------------------------------------------
void TTerminalManager::ReconnectingTerminal(TManagedTerminal * ManagedTerminal)
{
// particularly when we are reconnecting RemoteDirectory of managed terminal
// hold the last used remote directory as opposite to session data, which holds
// the default remote directory.
// make sure the last used directory is used, but the default is preserved too
ManagedTerminal->OrigRemoteDirectory = ManagedTerminal->SessionData->RemoteDirectory;
ManagedTerminal->SessionData->RemoteDirectory = ManagedTerminal->StateData->RemoteDirectory;
if ((double)ManagedTerminal->ReopenStart == 0)
{
ManagedTerminal->ReopenStart = Now();
}
}
//---------------------------------------------------------------------------
void TTerminalManager::ReconnectedTerminal(TManagedTerminal * ManagedTerminal)
{
ManagedTerminal->SessionData->RemoteDirectory = ManagedTerminal->OrigRemoteDirectory;
if (ManagedTerminal->Active)
{
ManagedTerminal->ReopenStart = 0;
ManagedTerminal->Permanent = true;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool Reopen, bool AdHoc)
{
TManagedTerminal * ManagedTerminal = dynamic_cast<TManagedTerminal *>(Terminal);
// it must be managed terminal, unless it is secondary terminal (of managed terminal)
DebugAssert((ManagedTerminal != NULL) || (dynamic_cast<TSecondaryTerminal *>(Terminal) != NULL));
// particularly when we are reconnecting RemoteDirectory of managed terminal
// hold the last used remote directory as opposite to session data, which holds
// the default remote directory.
// make sure the last used directory is used, but the default is preserved too
UnicodeString OrigRemoteDirectory = Terminal->SessionData->RemoteDirectory;
try
{
TValueRestorer<TTerminal *> OpeningTerminalRestorer(FOpeningTerminal, Terminal);
TTerminalThread * TerminalThread = new TTerminalThread(Terminal);
TerminalThread->AllowAbandon = (Terminal == ActiveTerminal);
TTerminalThread * TerminalThread;
bool ReconnectingInactiveTerminal = (Terminal == FReconnectingInactiveTerminal);
if (ReconnectingInactiveTerminal)
{
DebugAssert(Terminal == ActiveTerminal);
TerminalThread = ManagedTerminal->TerminalThread;
FReconnectingInactiveTerminal = NULL;
}
else
{
TerminalThread = CreateTerminalThread(Terminal);
}
try
{
if (ManagedTerminal != NULL)
TerminalThread->AllowAbandon = (Terminal == ActiveTerminal);
if (ReconnectingInactiveTerminal && DebugAlwaysTrue(Reopen))
{
Terminal->SessionData->RemoteDirectory = ManagedTerminal->StateData->RemoteDirectory;
if ((double)ManagedTerminal->ReopenStart == 0)
{
ManagedTerminal->ReopenStart = Now();
}
ManagedTerminal->Disconnected = false;
ManagedTerminal->DisconnectedTemporarily = false;
DebugAssert(ManagedTerminal->TerminalThread == NULL);
ManagedTerminal->TerminalThread = TerminalThread;
}
TNotifyEvent OnIdle;
((TMethod*)&OnIdle)->Code = TerminalThreadIdle;
TerminalThread->OnIdle = OnIdle;
if (Reopen)
{
TerminalThread->TerminalReopen();
TerminalThread->ContinueTerminalReopenInteractive();
}
else
{
TerminalThread->TerminalOpen();
if (ManagedTerminal != NULL)
{
ReconnectingTerminal(ManagedTerminal);
ManagedTerminal->Disconnected = false;
ManagedTerminal->DisconnectedTemporarily = false;
DebugAssert(ManagedTerminal->TerminalThread == NULL);
ManagedTerminal->TerminalThread = TerminalThread;
}
if (Reopen)
{
TerminalThread->TerminalReopen();
}
else
{
TerminalThread->TerminalOpen();
}
}
}
__finally
@@ -464,11 +499,9 @@ void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool R
}
__finally
{
Terminal->SessionData->RemoteDirectory = OrigRemoteDirectory;
if (Terminal->Active && (ManagedTerminal != NULL))
if (ManagedTerminal != NULL)
{
ManagedTerminal->ReopenStart = 0;
ManagedTerminal->Permanent = true;
ReconnectedTerminal(ManagedTerminal);
}
}
@@ -507,7 +540,10 @@ void __fastcall TTerminalManager::TerminalThreadIdle(void * /*Data*/, TObject *
//---------------------------------------------------------------------------
bool __fastcall TTerminalManager::ConnectActiveTerminalImpl(bool Reopen)
{
ActiveTerminal->CollectUsage();
if (ActiveTerminal != FReconnectingInactiveTerminal)
{
ActiveTerminal->CollectUsage();
}
TTerminalPendingAction Action;
bool Result;
@@ -657,12 +693,11 @@ void __fastcall TTerminalManager::ReconnectActiveTerminal()
{
DebugAssert(ActiveTerminal);
if (ScpExplorer)
if ((ScpExplorer != NULL) &&
DebugAlwaysTrue(ActiveTerminal != FReconnectingInactiveTerminal) &&
(ScpExplorer->Terminal == ActiveTerminal))
{
if (ScpExplorer->Terminal == ActiveTerminal)
{
ScpExplorer->UpdateSession(ActiveTerminal);
}
ScpExplorer->UpdateSession(ActiveTerminal);
}
try
@@ -701,9 +736,19 @@ void __fastcall TTerminalManager::FreeAll()
//---------------------------------------------------------------------------
void __fastcall TTerminalManager::FreeTerminal(TTerminal * Terminal)
{
TTerminal * ATerminal = Terminal;
bool IsActiveSession = ((ActiveSession != NULL) && (Terminal == ActiveSession));
try
{
if (!IsActiveSession)
{
// For active session, done by DoSetActiveSession below.
// For inactive session, needs to be done here before potentially abandoning the session.
SaveTerminal(Terminal);
}
TManagedTerminal * ManagedSession = DebugNotNull(dynamic_cast<TManagedTerminal *>(Terminal));
// we want the Login dialog to open on auto-workspace name,
// as set in TCustomScpExplorerForm::FormClose
if ((!FDestroying || !WinConfiguration->AutoSaveWorkspace) && !ManagedSession->LocalBrowser)
@@ -719,22 +764,46 @@ void __fastcall TTerminalManager::FreeTerminal(TTerminal * Terminal)
ScpExplorer->TerminalRemoved(Terminal);
}
if (Terminal->Active)
if (ManagedSession == FReconnectingInactiveTerminal)
{
DebugAssert(!IsActiveSession);
AppLog(L"Cancelling background reconnect before closing session");
TTerminalThread * TerminalThread = DebugNotNull(ManagedSession->TerminalThread);
DebugAssert(TerminalThread->AllowAbandon);
TerminalThread->Cancel();
while (!TerminalThread->Abandoned())
{
Sleep(50);
}
if (!TerminalThread->Release())
{
AppLog(L"Abandoned background reconnect");
TerminalThread->Terminate();
Terminal = NULL;
}
else
{
AppLog(L"Background reconnect stopped");
ManagedSession->TerminalThread = NULL;
}
FReconnectingInactiveTerminal = NULL;
}
else if (Terminal->Active)
{
Terminal->Close();
}
}
__finally
{
int Index = IndexOf(Terminal);
Extract(Terminal);
int Index = IndexOf(ATerminal);
Extract(ATerminal);
TTerminalQueue * Queue;
Queue = reinterpret_cast<TTerminalQueue *>(FQueues->Items[Index]);
FQueues->Delete(Index);
FTerminationMessages->Delete(Index);
if ((ActiveSession != NULL) && (Terminal == ActiveSession))
if (IsActiveSession)
{
TManagedTerminal * NewActiveTerminal;
bool LastTerminalClosed = false;
@@ -767,15 +836,11 @@ void __fastcall TTerminalManager::FreeTerminal(TTerminal * Terminal)
}
DoSetActiveSession(NewActiveTerminal, false, LastTerminalClosed);
}
else
{
SaveTerminal(Terminal);
}
// only now all references to/from queue (particularly events to explorer)
// are cleared
delete Queue;
delete Terminal;
delete Terminal; // noop if abandoned
DoSessionListChanged();
}
@@ -880,30 +945,30 @@ void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, b
if (ActiveSession != NULL)
{
int Index = ActiveSessionIndex;
if (!ActiveSession->Active &&
!FTerminationMessages->Strings[Index].IsEmpty() &&
DebugAlwaysTrue(!ActiveSession->LocalBrowser))
if ((FReconnectingInactiveTerminal == ActiveSession) &&
DebugAlwaysTrue(ScpExplorer != NULL) &&
DebugAlwaysTrue(FReconnectingInactiveTerminal->TerminalThread != NULL))
{
UnicodeString Message = FTerminationMessages->Strings[Index];
FTerminationMessages->Strings[Index] = L"";
if (AutoReconnect)
DebugAssert(FTerminalPendingAction == tpNull); // the only assumed and tested scenario
ReconnectActiveTerminal();
}
else if (!ActiveSession->Active &&
!ActiveSession->InactiveTerminationMessage.IsEmpty() &&
DebugAlwaysTrue(!ActiveSession->LocalBrowser))
{
UnicodeString Message = ActiveSession->InactiveTerminationMessage;
ActiveSession->InactiveTerminationMessage = EmptyStr;
if (AutoReconnect || GUIConfiguration->SessionReopenAutoInactive)
{
ReconnectActiveTerminal();
}
else
{
Exception * E = new ESshFatal(NULL, Message);
try
{
// finally show pending terminal message,
// this gives user also possibility to reconnect
ActiveTerminal->ShowExtendedException(E);
}
__finally
{
delete E;
}
// finally show pending terminal message,
// this gives user also possibility to reconnect
std::unique_ptr<ESshFatal> E(new ESshFatal(NULL, Message));
E->InactiveTerminationMessage = true;
ActiveTerminal->ShowExtendedException(E.get());
}
}
@@ -927,6 +992,7 @@ void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, b
if ((ActiveSession != NULL) &&
!ActiveSession->Active && !ActiveSession->Disconnected && !ActiveSession->LocalBrowser)
{
DebugAssert(ActiveSession->InactiveTerminationMessage.IsEmpty());
ConnectActiveTerminal();
}
}
@@ -1226,6 +1292,7 @@ void __fastcall TTerminalManager::TerminalQueryUser(TObject * Sender,
const UnicodeString Query, TStrings * MoreMessages, unsigned int Answers,
const TQueryParams * Params, unsigned int & Answer, TQueryType Type, void * /*Arg*/)
{
TTerminal * Terminal = DebugNotNull(dynamic_cast<TTerminal *>(Sender));
UnicodeString HelpKeyword;
TMessageParams MessageParams(Params);
UnicodeString AQuery = Query;
@@ -1248,7 +1315,7 @@ void __fastcall TTerminalManager::TerminalQueryUser(TObject * Sender,
if (ScpExplorer)
{
Answer = ScpExplorer->MoreMessageDialog(AQuery, MoreMessages, Type, Answers,
HelpKeyword, &MessageParams, dynamic_cast<TTerminal *>(Sender));
HelpKeyword, &MessageParams, Terminal);
}
else
{
@@ -1642,6 +1709,11 @@ UnicodeString __fastcall TTerminalManager::GetSessionTitle(TManagedTerminal * Se
return Result;
}
//---------------------------------------------------------------------------
UnicodeString TTerminalManager::FormatTerminalNoteMessage(TManagedTerminal * Terminal, const UnicodeString & Message)
{
return FORMAT(L"%s: %s", (GetSessionTitle(Terminal, true), Message));
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminalManager::GetActiveSessionAppTitle()
{
UnicodeString Result;
@@ -1782,6 +1854,90 @@ void __fastcall TTerminalManager::NewSession(
while (Retry);
}
//---------------------------------------------------------------------------
void TTerminalManager::StartInactiveTerminalReconnect(TManagedTerminal * Terminal)
{
ReconnectingTerminal(Terminal);
Terminal->NextInactiveReconnect = TDateTime(); // just in case
FReconnectingInactiveTerminal = Terminal;
DebugAssert(Terminal->TerminalThread == NULL);
TTerminalThread * TerminalThread = CreateTerminalThread(Terminal);
Terminal->TerminalThread = TerminalThread;
TerminalThread->AllowAbandon = true;
TerminalThread->StartTerminalReopenNonInteractive();
FReconnectingNote = FormatTerminalNoteMessage(Terminal, LoadStr(SESSION_RECONNECTING));
ScpExplorer->PostNote(FReconnectingNote);
}
//---------------------------------------------------------------------------
void TTerminalManager::CheckInactiveTerminalReconnect()
{
TManagedTerminal * Terminal = DebugNotNull(FReconnectingInactiveTerminal);
TTerminalThread * TerminalThread = DebugNotNull(Terminal->TerminalThread);
TTerminalReopenResult ReopenResult = TerminalThread->IsTerminalReopenComplete();
UnicodeString SessionName = Terminal->SessionData->SessionName;
if (ReopenResult == trrPending)
{
AppLog(FORMAT(L"Inactive session %s is still being reconnected", (SessionName)));
}
else
{
try
{
DebugCheck(TerminalThread->Release());
}
__finally
{
ReconnectedTerminal(Terminal);
Terminal->TerminalThread = NULL;
FReconnectingInactiveTerminal = NULL;
}
if (ReopenResult == trrSucceeded)
{
AppLog(FORMAT(L"Inactive session %s was reconnected", (SessionName)));
ScpExplorer->CancelNote(FReconnectingNote);
ScpExplorer->InactiveTerminalNotify(Terminal, LoadStr(SESSION_RECONNECTED), qtInformation);
}
else
{
if (ReopenResult == trrFailed)
{
// in case it was disabled meanwhile
if (GUIConfiguration->SessionReopenAutoInactive)
{
int Interval = 2 * GUIConfiguration->SessionReopenAutoIdle;
AppLog(FORMAT(L"Inactive session %s failed to reconnect, will try again in %d ms", (SessionName, Interval)));
// Might want to consider SessionReopenTimeout
Terminal->NextInactiveReconnect = IncMilliSecond(Now(), Interval);
}
else
{
Terminal->NextInactiveReconnect = TDateTime();
}
}
else if (ReopenResult == trrNeedsInteraction)
{
AppLog(FORMAT(L"Inactive session %s needs interaction", (SessionName)));
ScpExplorer->CancelNote(FReconnectingNote);
ScpExplorer->InactiveTerminalNotify(Terminal, LoadStr(SESSION_NEED_INTERACTION), qtInformation);
Terminal->NextInactiveReconnect = TDateTime();
}
try
{
if (Terminal->Active)
{
Terminal->Close();
}
}
catch (...)
{
}
}
FReconnectingNote = EmptyStr;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminalManager::Idle(bool SkipCurrentTerminal)
{
@@ -1809,7 +1965,11 @@ void __fastcall TTerminalManager::Idle(bool SkipCurrentTerminal)
TManagedTerminal * Terminal = Sessions[Index];
try
{
if (!SkipCurrentTerminal || (Terminal != ActiveTerminal))
if (Terminal == FReconnectingInactiveTerminal)
{
CheckInactiveTerminalReconnect();
}
else if (!SkipCurrentTerminal || (Terminal != ActiveTerminal))
{
// make sure Idle is called on the thread that runs the terminal
if (Terminal->TerminalThread != NULL)
@@ -1822,6 +1982,14 @@ void __fastcall TTerminalManager::Idle(bool SkipCurrentTerminal)
{
Terminal->Idle();
}
else if ((Terminal != ActiveTerminal) &&
GUIConfiguration->SessionReopenAutoInactive &&
(FReconnectingInactiveTerminal == NULL) &&
(Terminal->NextInactiveReconnect != TDateTime()) &&
(Terminal->NextInactiveReconnect < Now()))
{
StartInactiveTerminalReconnect(Terminal);
}
}
if (Terminal->Active)
@@ -1846,17 +2014,17 @@ void __fastcall TTerminalManager::Idle(bool SkipCurrentTerminal)
{
// we may not have inactive terminal, unless there is a explorer,
// also Idle is called from explorer anyway
DebugAssert(ScpExplorer != NULL);
if (ScpExplorer != NULL)
if (DebugAlwaysTrue(ScpExplorer != NULL))
{
ScpExplorer->InactiveTerminalException(Terminal, &E);
ScpExplorer->InactiveTerminalNotify(Terminal, EmptyStr, qtError, &E);
}
if (!Terminal->Active)
{
// if session is lost, save the error message and rethrow it
// once the terminal gets activated
FTerminationMessages->Strings[Index] = E.Message;
Terminal->InactiveTerminationMessage = E.Message;
Terminal->NextInactiveReconnect = IncMilliSecond(Now(), GUIConfiguration->SessionReopenAutoIdle);
}
}
}
@@ -1951,9 +2119,24 @@ void __fastcall TTerminalManager::SaveWorkspace(TList * DataList)
}
}
//---------------------------------------------------------------------------
bool TTerminalManager::IsActiveTerminal(TTerminal * Terminal)
{
return
(Terminal != NULL) &&
Terminal->Active &&
!IsReconnectingTerminal(Terminal);
}
//---------------------------------------------------------------------------
bool TTerminalManager::IsReconnectingTerminal(TTerminal * Terminal)
{
return
(Terminal != NULL) &&
((Terminal == FOpeningTerminal) || (Terminal == FReconnectingInactiveTerminal));
}
//---------------------------------------------------------------------------
bool __fastcall TTerminalManager::IsActiveTerminalForSite(TTerminal * Terminal, TSessionData * Data)
{
bool Result = Terminal->Active;
bool Result = IsActiveTerminal(Terminal);
if (Result)
{
std::unique_ptr<TSessionData> TerminalData(Terminal->SessionData->Clone());

View File

@@ -64,6 +64,10 @@ public:
std::unique_ptr<TCriticalSection> ThumbnailsSection;
bool ThumbnailsEnabled;
TThumbnailDownloadQueueItem * ThumbnailDownloadQueueItem;
UnicodeString InactiveTerminationMessage;
TDateTime NextInactiveReconnect;
TDateTime LastInactiveSave;
UnicodeString OrigRemoteDirectory;
TRemoteThumbnailsMap Thumbnails;
TRemoteThumbnailsQueue ThumbnailsQueue;
@@ -103,12 +107,15 @@ public:
void NewLocalSession(const UnicodeString & LocalDirectory = UnicodeString(), const UnicodeString & OtherLocalDirectory = UnicodeString());
void __fastcall Idle(bool SkipCurrentTerminal);
UnicodeString __fastcall GetSessionTitle(TManagedTerminal * Terminal, bool Unique);
UnicodeString FormatTerminalNoteMessage(TManagedTerminal * Terminal, const UnicodeString & Message);
UnicodeString __fastcall GetActiveSessionAppTitle();
UnicodeString __fastcall GetAppProgressTitle();
UnicodeString __fastcall FormatFormCaptionWithSession(TCustomForm * Form, const UnicodeString & Caption);
void __fastcall HandleException(Exception * E);
void __fastcall SaveWorkspace(TList * DataList);
void __fastcall QueueStatusUpdated();
bool IsActiveTerminal(TTerminal * Terminal);
bool IsReconnectingTerminal(TTerminal * Terminal);
bool __fastcall IsActiveTerminalForSite(TTerminal * Terminal, TSessionData * Data);
TManagedTerminal * __fastcall FindActiveTerminalForSite(TSessionData * Data);
TTerminalQueue * __fastcall FindQueueForTerminal(TTerminal * Terminal);
@@ -129,6 +136,7 @@ public:
__property TTerminal * LocalTerminal = { read = FLocalTerminal };
__property TManagedTerminal * Sessions[int Index] = { read = GetSession };
__property bool Updating = { read = IsUpdating };
__property TManagedTerminal * ReconnectingInactiveTerminal = { read = FReconnectingInactiveTerminal };
protected:
virtual TTerminal * __fastcall CreateTerminal(TSessionData * Data);
@@ -146,7 +154,6 @@ private:
TTerminalPendingAction FTerminalPendingAction;
TStrings * FSessionList;
TList * FQueues;
TStrings * FTerminationMessages;
UnicodeString FProgressTitle;
UnicodeString FForegroundProgressTitle;
TDateTime FDirectoryReadingStart;
@@ -166,6 +173,8 @@ private:
int FUpdating;
int FMaxSessions;
TTerminal * FOpeningTerminal;
TManagedTerminal * FReconnectingInactiveTerminal;
UnicodeString FReconnectingNote;
bool __fastcall ConnectActiveTerminalImpl(bool Reopen);
bool __fastcall ConnectActiveTerminal();
@@ -239,6 +248,11 @@ private:
bool SupportedSession(TSessionData * Data);
void __fastcall TerminalFatalExceptionTimer(unsigned int & Result);
void NeedThumbnailDownloadQueueItem(TManagedTerminal * ATerminal);
void StartInactiveTerminalReconnect(TManagedTerminal * Terminal);
void CheckInactiveTerminalReconnect();
TTerminalThread * CreateTerminalThread(TTerminal * Terminal);
void ReconnectingTerminal(TManagedTerminal * ManagedTerminal);
void ReconnectedTerminal(TManagedTerminal * ManagedTerminal);
};
//---------------------------------------------------------------------------
class TThumbnailDownloadQueueItem : public TTransferQueueItem

View File

@@ -218,8 +218,9 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
ESshTerminate * Terminate = dynamic_cast<ESshTerminate*>(E);
bool CloseOnCompletion = (Terminate != NULL);
EFatal * FatalException = dynamic_cast<EFatal *>(E);
bool ForActiveTerminal =
E->InheritsFrom(__classid(EFatal)) && (Terminal != NULL) &&
(FatalException != NULL) && (Terminal != NULL) &&
(Manager != NULL) && (Manager->ActiveTerminal == Terminal);
unsigned int Result;
@@ -281,6 +282,12 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
Result =
ExceptionMessageDialog(E, qtInformation, L"", Answers | qaOK, HELP_NONE, &Params);
}
if (Result == qaNeverAskAgain)
{
Result = qaYes;
WinConfiguration->ConfirmExitOnCompletion = false;
}
}
else
{
@@ -302,13 +309,27 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
((double)Manager->ActiveTerminal->ReopenStart == 0) ||
(int(double(Now() - Manager->ActiveTerminal->ReopenStart) * MSecsPerDay) < Configuration->SessionReopenTimeout)))
{
Params.Timeout = GUIConfiguration->SessionReopenAutoIdle;
Params.Timeout = GUIConfiguration->SessionReopenAutoIdleOn ? GUIConfiguration->SessionReopenAutoIdle : 0;
Params.TimeoutAnswer = qaRetry;
Params.TimeoutResponse = Params.TimeoutAnswer;
HookedDialog = Manager->HookFatalExceptionMessageDialog(Params);
}
bool InactiveTerminationMessage = (FatalException != NULL) && FatalException->InactiveTerminationMessage;
if (InactiveTerminationMessage)
{
Params.Params |= mpNeverAskAgainCheck;
Params.NeverAskAgainTitle = LoadStr(ALWAYS_RECONNECT);
Params.NeverAskAgainAnswer = qaRetry;
}
Result = FatalExceptionMessageDialog(E, qtError, EmptyStr, qaOK, EmptyStr, &Params);
if ((Result == qaNeverAskAgain) && DebugAlwaysTrue(InactiveTerminationMessage))
{
GUIConfiguration->SessionReopenAutoInactive = true;
Result = qaRetry;
}
}
__finally
{
@@ -329,13 +350,6 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
}
}
if (Result == qaNeverAskAgain)
{
DebugAssert(CloseOnCompletion);
Result = qaYes;
WinConfiguration->ConfirmExitOnCompletion = false;
}
if (Result == qaYes)
{
DebugAssert(CloseOnCompletion);