Explorar o código

Bug 1858: Autoreconnect a disconnected session when saving edited file

https://winscp.net/tracker/1858

Source commit: 8091728681083a9f3c72a0705ee5165365e7f173
Martin Prikryl %!s(int64=2) %!d(string=hai) anos
pai
achega
b3be1615ac

+ 63 - 53
source/forms/CustomScpExplorer.cpp

@@ -3886,7 +3886,14 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
   const UnicodeString & FileName, TEditedFileData * Data, HANDLE UploadCompleteEvent, bool & Retry)
 {
   TTerminalManager * Manager = TTerminalManager::Instance();
-  if (!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))
   {
     if (!NonVisualDataModule->Busy)
     {
@@ -3954,75 +3961,78 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(
     }
   }
 
-  if (EditorCheckNotModified(Data))
+  if (!Retry)
   {
-    UnicodeString RemoteFilePath = UnixCombinePaths(Data->RemoteDirectory, Data->OriginalFileName);
-    std::unique_ptr<TRemoteFile> File(Terminal->TryReadFile(RemoteFilePath));
-    if (File.get() != NULL)
+    if (EditorCheckNotModified(Data))
     {
-      AppLogFmt(L"Edited remote file timestamp: %s, Original timestamp: %s", (StandardTimestamp(File->Modification), StandardTimestamp(Data->SourceTimestamp)));
-
-      if (File->Modification != Data->SourceTimestamp)
+      UnicodeString RemoteFilePath = UnixCombinePaths(Data->RemoteDirectory, Data->OriginalFileName);
+      std::unique_ptr<TRemoteFile> File(Terminal->TryReadFile(RemoteFilePath));
+      if (File.get() != NULL)
       {
-        UnicodeString Message = MainInstructions(LoadStr(EDIT_CHANGED_EXTERNALLY));
-        if (MessageDialog(Message, qtConfirmation, qaOK | qaCancel) != qaOK)
+        AppLogFmt(L"Edited remote file timestamp: %s, Original timestamp: %s", (StandardTimestamp(File->Modification), StandardTimestamp(Data->SourceTimestamp)));
+
+        if (File->Modification != Data->SourceTimestamp)
         {
-          Abort();
+          UnicodeString Message = MainInstructions(LoadStr(EDIT_CHANGED_EXTERNALLY));
+          if (MessageDialog(Message, qtConfirmation, qaOK | qaCancel) != qaOK)
+          {
+            Abort();
+          }
         }
       }
     }
-  }
 
-  TStrings * FileList = new TStringList();
-  try
-  {
-    FileList->Add(FileName);
-
-    // Consider using the same settings (preset) as when the file was downloaded.
-    // More over this does not reflect the actual session that will do the upload.
-    TGUICopyParamType CopyParam = GUIConfiguration->CurrentCopyParam;
-    TemporaryFileCopyParam(CopyParam);
-    if (Data->ForceText)
-    {
-      CopyParam.TransferMode = tmAscii;
-    }
-    // so i do not need to worry if masking algorithm works in all cases
-    // ("" means "copy file name", no masking is actually done)
-    if (ExtractFileName(FileName) == Data->OriginalFileName)
-    {
-      CopyParam.FileMask = L"";
-    }
-    else
+    TStrings * FileList = new TStringList();
+    try
     {
-      CopyParam.FileMask = DelimitFileNameMask(Data->OriginalFileName);
-    }
+      FileList->Add(FileName);
 
-    int Params = cpNoConfirmation | cpTemporary;
-    if (Data->Terminal->IsCapable[fcBackgroundTransfers])
-    {
-      DebugAssert(Data->Queue != NULL);
+      // Consider using the same settings (preset) as when the file was downloaded.
+      // More over this does not reflect the actual session that will do the upload.
+      TGUICopyParamType CopyParam = GUIConfiguration->CurrentCopyParam;
+      TemporaryFileCopyParam(CopyParam);
+      if (Data->ForceText)
+      {
+        CopyParam.TransferMode = tmAscii;
+      }
+      // so i do not need to worry if masking algorithm works in all cases
+      // ("" means "copy file name", no masking is actually done)
+      if (ExtractFileName(FileName) == Data->OriginalFileName)
+      {
+        CopyParam.FileMask = L"";
+      }
+      else
+      {
+        CopyParam.FileMask = DelimitFileNameMask(Data->OriginalFileName);
+      }
 
-      TQueueItem * QueueItem = new TUploadQueueItem(Data->Terminal, FileList,
-        Data->RemoteDirectory, &CopyParam, Params, true, false);
-      QueueItem->CompleteEvent = UploadCompleteEvent;
-      AddQueueItem(Data->Queue, QueueItem, Data->Terminal);
-    }
-    else
-    {
-      if (NonVisualDataModule->Busy)
+      int Params = cpNoConfirmation | cpTemporary;
+      if (Data->Terminal->IsCapable[fcBackgroundTransfers])
       {
-        Retry = true;
+        DebugAssert(Data->Queue != NULL);
+
+        TQueueItem * QueueItem = new TUploadQueueItem(Data->Terminal, FileList,
+          Data->RemoteDirectory, &CopyParam, Params, true, false);
+        QueueItem->CompleteEvent = UploadCompleteEvent;
+        AddQueueItem(Data->Queue, QueueItem, Data->Terminal);
       }
       else
       {
-        Data->Terminal->CopyToRemote(FileList, Data->RemoteDirectory, &CopyParam, Params, NULL);
-        SetEvent(UploadCompleteEvent);
+        if (NonVisualDataModule->Busy)
+        {
+          Retry = true;
+        }
+        else
+        {
+          Data->Terminal->CopyToRemote(FileList, Data->RemoteDirectory, &CopyParam, Params, NULL);
+          SetEvent(UploadCompleteEvent);
+        }
       }
     }
-  }
-  __finally
-  {
-    delete FileList;
+    __finally
+    {
+      delete FileList;
+    }
   }
 }
 //---------------------------------------------------------------------------

+ 43 - 0
source/windows/TerminalManager.cpp

@@ -67,6 +67,7 @@ __fastcall TTerminalManager::TTerminalManager() :
 {
   FQueueSection = new TCriticalSection();
   FActiveSession = NULL;
+  FTerminalWithFatalExceptionTimer = NULL;
   FScpExplorer = NULL;
   FDestroying = false;
   FTerminalPendingAction = tpNull;
@@ -1982,3 +1983,45 @@ bool TTerminalManager::IsUpdating()
 {
   return (FUpdating > 0);
 }
+//---------------------------------------------------------------------------
+bool TTerminalManager::HookFatalExceptionMessageDialog(TMessageParams & Params)
+{
+  bool Result =
+    DebugAlwaysTrue(ActiveTerminal != NULL) &&
+    DebugAlwaysTrue(Params.Timer == 0) &&
+    DebugAlwaysTrue(Params.TimerEvent == NULL) &&
+    DebugAlwaysTrue(FTerminalWithFatalExceptionTimer == NULL);
+  if (Result)
+  {
+    Params.Timer = MSecsPerSec / 4;
+    Params.TimerEvent = TerminalFatalExceptionTimer;
+    FTerminalWithFatalExceptionTimer = ActiveTerminal;
+    FTerminalReconnnecteScheduled = false;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void TTerminalManager::UnhookFatalExceptionMessageDialog()
+{
+  DebugAssert(FTerminalWithFatalExceptionTimer != NULL);
+  FTerminalWithFatalExceptionTimer = NULL;
+}
+//---------------------------------------------------------------------------
+bool TTerminalManager::ScheduleTerminalReconnnect(TTerminal * Terminal)
+{
+  bool Result = (FTerminalWithFatalExceptionTimer == Terminal);
+  if (Result)
+  {
+    FTerminalReconnnecteScheduled = true;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TTerminalManager::TerminalFatalExceptionTimer(unsigned int & Result)
+{
+  if (FTerminalReconnnecteScheduled)
+  {
+    Result = qaRetry;
+    FTerminalReconnnecteScheduled = false;
+  }
+}

+ 6 - 0
source/windows/TerminalManager.h

@@ -80,6 +80,9 @@ public:
   TTerminalQueue * __fastcall FindQueueForTerminal(TTerminal * Terminal);
   bool __fastcall UploadPublicKey(TTerminal * Terminal, TSessionData * Data, UnicodeString & FileName);
   UnicodeString GetPathForSessionTabName(const UnicodeString & Result);
+  bool HookFatalExceptionMessageDialog(TMessageParams & Params);
+  void UnhookFatalExceptionMessageDialog();
+  bool ScheduleTerminalReconnnect(TTerminal * Terminal);
 
   __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer };
   __property TManagedTerminal * ActiveSession = { read = FActiveSession, write = SetActiveSession };
@@ -100,6 +103,8 @@ private:
   static TTerminalManager * FInstance;
   TCustomScpExplorerForm * FScpExplorer;
   TManagedTerminal * FActiveSession;
+  TManagedTerminal * FTerminalWithFatalExceptionTimer;
+  bool FTerminalReconnnecteScheduled;
   TTerminal * FLocalTerminal;
   bool FDestroying;
   TTerminalPendingAction FTerminalPendingAction;
@@ -195,6 +200,7 @@ private:
   TManagedTerminal * __fastcall GetSession(int Index);
   bool IsUpdating();
   bool SupportedSession(TSessionData * Data);
+  void __fastcall TerminalFatalExceptionTimer(unsigned int & Result);
 };
 //---------------------------------------------------------------------------
 #endif

+ 126 - 112
source/windows/UserInterface.cpp

@@ -212,156 +212,170 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
     // swallow
   }
 
-  if (!DoNotDisplay)
+  TTerminalManager * Manager = TTerminalManager::Instance(false);
+  bool HookedDialog = false;
+  try
   {
-    TTerminalManager * Manager = TTerminalManager::Instance(false);
-
-    ESshTerminate * Terminate = dynamic_cast<ESshTerminate*>(E);
-    bool CloseOnCompletion = (Terminate != NULL);
-
-    bool ForActiveTerminal =
-      E->InheritsFrom(__classid(EFatal)) && (Terminal != NULL) &&
-      (Manager != NULL) && (Manager->ActiveTerminal == Terminal);
-
-    unsigned int Result;
-    if (CloseOnCompletion)
+    if (!DoNotDisplay)
     {
-      if (ForActiveTerminal)
-      {
-        DebugAssert(!Terminal->Active);
-        Manager->DisconnectActiveTerminal();
-      }
+      ESshTerminate * Terminate = dynamic_cast<ESshTerminate*>(E);
+      bool CloseOnCompletion = (Terminate != NULL);
 
-      if (Terminate->Operation == odoSuspend)
-      {
-        // suspend, so that exit prompt is shown only after windows resume
-        SuspendWindows();
-      }
+      bool ForActiveTerminal =
+        E->InheritsFrom(__classid(EFatal)) && (Terminal != NULL) &&
+        (Manager != NULL) && (Manager->ActiveTerminal == Terminal);
 
-      DebugAssert(Show);
-      bool ConfirmExitOnCompletion =
-        CloseOnCompletion &&
-        ((Terminate->Operation == odoDisconnect) || (Terminate->Operation == odoSuspend)) &&
-        WinConfiguration->ConfirmExitOnCompletion;
-
-      if (ConfirmExitOnCompletion)
+      unsigned int Result;
+      if (CloseOnCompletion)
       {
-        TMessageParams Params(mpNeverAskAgainCheck);
-        unsigned int Answers = 0;
-        TQueryButtonAlias Aliases[1];
-        TOpenLocalPathHandler OpenLocalPathHandler;
-        if (!Terminate->TargetLocalPath.IsEmpty() && !ForActiveTerminal)
+        if (ForActiveTerminal)
         {
-          OpenLocalPathHandler.LocalPath = Terminate->TargetLocalPath;
-          OpenLocalPathHandler.LocalFileName = Terminate->DestLocalFileName;
-
-          Aliases[0].Button = qaIgnore;
-          Aliases[0].Alias = LoadStr(OPEN_BUTTON);
-          Aliases[0].OnSubmit = OpenLocalPathHandler.Open;
-          Aliases[0].MenuButton = true;
-          Answers |= Aliases[0].Button;
-          Params.Aliases = Aliases;
-          Params.AliasesCount = LENOF(Aliases);
+          DebugAssert(!Terminal->Active);
+          Manager->DisconnectActiveTerminal();
         }
 
-        if (ForActiveTerminal)
+        if (Terminate->Operation == odoSuspend)
         {
-          UnicodeString MessageFormat =
-            (Manager->Count > 1) ?
-              FMTLOAD(DISCONNECT_ON_COMPLETION, (Manager->Count - 1)) :
-              LoadStr(EXIT_ON_COMPLETION);
-          // Remove the leading "%s\n\n" (not to change the translation originals - previously the error message was prepended)
-          MessageFormat = FORMAT(MessageFormat, (UnicodeString())).Trim();
-          MessageFormat = MainInstructions(MessageFormat) + L"\n\n%s";
-          Result = FatalExceptionMessageDialog(E, qtInformation, 0,
-            MessageFormat,
-            Answers | qaYes | qaNo, HELP_NONE, &Params);
+          // suspend, so that exit prompt is shown only after windows resume
+          SuspendWindows();
+        }
+
+        DebugAssert(Show);
+        bool ConfirmExitOnCompletion =
+          CloseOnCompletion &&
+          ((Terminate->Operation == odoDisconnect) || (Terminate->Operation == odoSuspend)) &&
+          WinConfiguration->ConfirmExitOnCompletion;
+
+        if (ConfirmExitOnCompletion)
+        {
+          TMessageParams Params(mpNeverAskAgainCheck);
+          unsigned int Answers = 0;
+          TQueryButtonAlias Aliases[1];
+          TOpenLocalPathHandler OpenLocalPathHandler;
+          if (!Terminate->TargetLocalPath.IsEmpty() && !ForActiveTerminal)
+          {
+            OpenLocalPathHandler.LocalPath = Terminate->TargetLocalPath;
+            OpenLocalPathHandler.LocalFileName = Terminate->DestLocalFileName;
+
+            Aliases[0].Button = qaIgnore;
+            Aliases[0].Alias = LoadStr(OPEN_BUTTON);
+            Aliases[0].OnSubmit = OpenLocalPathHandler.Open;
+            Aliases[0].MenuButton = true;
+            Answers |= Aliases[0].Button;
+            Params.Aliases = Aliases;
+            Params.AliasesCount = LENOF(Aliases);
+          }
+
+          if (ForActiveTerminal)
+          {
+            UnicodeString MessageFormat =
+              (Manager->Count > 1) ?
+                FMTLOAD(DISCONNECT_ON_COMPLETION, (Manager->Count - 1)) :
+                LoadStr(EXIT_ON_COMPLETION);
+            // Remove the leading "%s\n\n" (not to change the translation originals - previously the error message was prepended)
+            MessageFormat = FORMAT(MessageFormat, (UnicodeString())).Trim();
+            MessageFormat = MainInstructions(MessageFormat) + L"\n\n%s";
+            Result = FatalExceptionMessageDialog(E, qtInformation,
+              MessageFormat,
+              Answers | qaYes | qaNo, HELP_NONE, &Params);
+          }
+          else
+          {
+            Result =
+              ExceptionMessageDialog(E, qtInformation, L"", Answers | qaOK, HELP_NONE, &Params);
+          }
         }
         else
         {
-          Result =
-            ExceptionMessageDialog(E, qtInformation, L"", Answers | qaOK, HELP_NONE, &Params);
+          Result = qaYes;
         }
       }
       else
       {
-        Result = qaYes;
-      }
-    }
-    else
-    {
-      if (Show)
-      {
-        if (ForActiveTerminal)
+        if (Show)
         {
-          int SessionReopenTimeout = 0;
-          if (DebugAlwaysTrue(Manager->ActiveTerminal != NULL) &&
-              ((Configuration->SessionReopenTimeout == 0) ||
-               ((double)Manager->ActiveTerminal->ReopenStart == 0) ||
-               (int(double(Now() - Manager->ActiveTerminal->ReopenStart) * MSecsPerDay) < Configuration->SessionReopenTimeout)))
+          if (ForActiveTerminal)
+          {
+            TMessageParams Params;
+            if (DebugAlwaysTrue(Manager->ActiveTerminal != NULL) &&
+                ((Configuration->SessionReopenTimeout == 0) ||
+                 ((double)Manager->ActiveTerminal->ReopenStart == 0) ||
+                 (int(double(Now() - Manager->ActiveTerminal->ReopenStart) * MSecsPerDay) < Configuration->SessionReopenTimeout)))
+            {
+              Params.Timeout = GUIConfiguration->SessionReopenAutoIdle;
+              Params.TimeoutAnswer = qaRetry;
+              Params.TimeoutResponse = Params.TimeoutAnswer;
+              HookedDialog = Manager->HookFatalExceptionMessageDialog(Params);
+            }
+
+            Result = FatalExceptionMessageDialog(E, qtError, EmptyStr, qaOK, EmptyStr, &Params);
+          }
+          else
           {
-            SessionReopenTimeout = GUIConfiguration->SessionReopenAutoIdle;
+            Result = ExceptionMessageDialog(E, qtError);
           }
-          Result = FatalExceptionMessageDialog(E, qtError, SessionReopenTimeout);
         }
         else
         {
-          Result = ExceptionMessageDialog(E, qtError);
+          Result = qaOK;
         }
       }
-      else
+
+      if (Result == qaNeverAskAgain)
       {
-        Result = qaOK;
+        DebugAssert(CloseOnCompletion);
+        Result = qaYes;
+        WinConfiguration->ConfirmExitOnCompletion = false;
       }
-    }
-
-    if (Result == qaNeverAskAgain)
-    {
-      DebugAssert(CloseOnCompletion);
-      Result = qaYes;
-      WinConfiguration->ConfirmExitOnCompletion = false;
-    }
 
-    if (Result == qaYes)
-    {
-      DebugAssert(CloseOnCompletion);
-      DebugAssert(Terminate != NULL);
-      DebugAssert(Terminate->Operation != odoIdle);
-      TerminateApplication();
-
-      switch (Terminate->Operation)
+      if (Result == qaYes)
       {
-        case odoDisconnect:
-          break;
+        DebugAssert(CloseOnCompletion);
+        DebugAssert(Terminate != NULL);
+        DebugAssert(Terminate->Operation != odoIdle);
+        TerminateApplication();
 
-        case odoSuspend:
-          // suspended before already
-          break;
+        switch (Terminate->Operation)
+        {
+          case odoDisconnect:
+            break;
 
-        case odoShutDown:
-          ShutDownWindows();
-          break;
+          case odoSuspend:
+            // suspended before already
+            break;
 
-        default:
-          DebugFail();
+          case odoShutDown:
+            ShutDownWindows();
+            break;
+
+          default:
+            DebugFail();
+        }
       }
-    }
-    else if (Result == qaRetry)
-    {
-      // qaRetry is used by FatalExceptionMessageDialog
-      if (DebugAlwaysTrue(ForActiveTerminal))
+      else if (Result == qaRetry)
       {
-        Manager->ReconnectActiveTerminal();
+        // qaRetry is used by FatalExceptionMessageDialog
+        if (DebugAlwaysTrue(ForActiveTerminal))
+        {
+          Manager->ReconnectActiveTerminal();
+        }
       }
-    }
-    else
-    {
-      if (ForActiveTerminal)
+      else
       {
-        Manager->DisconnectActiveTerminalIfPermanentFreeOtherwise();
+        if (ForActiveTerminal)
+        {
+          Manager->DisconnectActiveTerminalIfPermanentFreeOtherwise();
+        }
       }
     }
   }
+  __finally
+  {
+    if (HookedDialog)
+    {
+      Manager->UnhookFatalExceptionMessageDialog();
+    }
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall ShowNotification(TTerminal * Terminal, const UnicodeString & Str,

+ 3 - 11
source/windows/WinInterface.cpp

@@ -655,9 +655,9 @@ unsigned int __fastcall ExceptionMessageDialog(Exception * E, TQueryType Type,
     Message, MoreMessages, Type, Answers, HelpKeyword, Params);
 }
 //---------------------------------------------------------------------------
-unsigned int __fastcall FatalExceptionMessageDialog(Exception * E, TQueryType Type,
-  int SessionReopenTimeout, const UnicodeString MessageFormat, unsigned int Answers,
-  UnicodeString HelpKeyword, const TMessageParams * Params)
+unsigned int __fastcall FatalExceptionMessageDialog(
+  Exception * E, TQueryType Type, const UnicodeString & MessageFormat, unsigned int Answers,
+  const UnicodeString & HelpKeyword, const TMessageParams * Params)
 {
   DebugAssert(FLAGCLEAR(Answers, qaRetry));
   Answers |= qaRetry;
@@ -671,14 +671,6 @@ unsigned int __fastcall FatalExceptionMessageDialog(Exception * E, TQueryType Ty
   {
     AParams = *Params;
   }
-  DebugAssert(AParams.Timeout == 0);
-  // the condition is de facto excess
-  if (SessionReopenTimeout > 0)
-  {
-    AParams.Timeout = SessionReopenTimeout;
-    AParams.TimeoutAnswer = qaRetry;
-    AParams.TimeoutResponse = AParams.TimeoutAnswer;
-  }
   DebugAssert(AParams.Aliases == NULL);
   AParams.Aliases = Aliases;
   AParams.AliasesCount = LENOF(Aliases);

+ 3 - 3
source/windows/WinInterface.h

@@ -141,9 +141,9 @@ unsigned int __fastcall MoreMessageDialog(const UnicodeString Message,
 unsigned int __fastcall ExceptionMessageDialog(Exception * E, TQueryType Type,
   const UnicodeString MessageFormat = L"", unsigned int Answers = qaOK,
   UnicodeString HelpKeyword = HELP_NONE, const TMessageParams * Params = NULL);
-unsigned int __fastcall FatalExceptionMessageDialog(Exception * E, TQueryType Type,
-  int SessionReopenTimeout, const UnicodeString MessageFormat = L"", unsigned int Answers = qaOK,
-  UnicodeString HelpKeyword = HELP_NONE, const TMessageParams * Params = NULL);
+unsigned int __fastcall FatalExceptionMessageDialog(
+  Exception * E, TQueryType Type, const UnicodeString & MessageFormat, unsigned int Answers,
+  const UnicodeString & HelpKeyword, const TMessageParams * Params);
 
 // forms\Custom.cpp
 TSessionData * __fastcall DoSaveSession(TSessionData * SessionData,