Переглянути джерело

Bug 1594: Custom error handling instead of default abort can be optionally implemented for batch operations in .NET assembly

https://winscp.net/tracker/1594

Source commit: 0cce3541a5e42f3df57071f88d07c2c9c473064a
Martin Prikryl 7 роки тому
батько
коміт
b57798e2c8

+ 2 - 0
dotnet/GlobalSuppressions.cs

@@ -169,3 +169,5 @@ using System.Diagnostics.CodeAnalysis;
 [assembly: SuppressMessage("Microsoft.Interoperability", "CA1407:AvoidStaticMembersInComVisibleTypes", Scope = "member", Target = "WinSCP.RemotePath.#GetDirectoryName(System.String)")]
 [assembly: SuppressMessage("Microsoft.Interoperability", "CA1407:AvoidStaticMembersInComVisibleTypes", Scope = "member", Target = "WinSCP.RemotePath.#AddDirectorySeparator(System.String)")]
 [assembly: SuppressMessage("Microsoft.Interoperability", "CA1407:AvoidStaticMembersInComVisibleTypes", Scope = "member", Target = "WinSCP.RemotePath.#GetFileName(System.String)")]
+[assembly: SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Scope = "type", Target = "WinSCP.QueryReceivedEventArgs")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1003:UseGenericEventHandlerInstances", Scope = "type", Target = "WinSCP.QueryReceivedEventHandler")]

+ 31 - 0
dotnet/QueryReceivedEventArgs.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace WinSCP
+{
+    [Guid("1C2C3740-CB42-4B10-B240-2EF64E03DAA3")]
+    [ClassInterface(Constants.ClassInterface)]
+    [ComVisible(true)]
+    public sealed class QueryReceivedEventArgs : EventArgs
+    {
+        public string Message { get; internal set; }
+
+        internal enum Action { None, Abort, Continue }
+        internal Action SelectedAction { get; set; }
+
+        internal QueryReceivedEventArgs()
+        {
+            SelectedAction = Action.None;
+        }
+
+        public void Abort()
+        {
+            SelectedAction = Action.Abort;
+        }
+
+        public void Continue()
+        {
+            SelectedAction = Action.Continue;
+        }
+    }
+}

+ 63 - 1
dotnet/Session.cs

@@ -56,6 +56,7 @@ namespace WinSCP
     public delegate void FileTransferredEventHandler(object sender, TransferEventArgs e);
     public delegate void FileTransferProgressEventHandler(object sender, FileTransferProgressEventArgs e);
     public delegate void FailedEventHandler(object sender, FailedEventArgs e);
+    public delegate void QueryReceivedEventHandler(object sender, QueryReceivedEventArgs e);
 
     [Guid("56FFC5CE-3867-4EF0-A3B5-CFFBEB99EA35")]
     [ClassInterface(Constants.ClassInterface)]
@@ -115,6 +116,36 @@ namespace WinSCP
             }
         }
 
+        public event QueryReceivedEventHandler QueryReceived { add { AddQueryReceived(value); } remove { RemoveQueryReceived(value); } }
+
+        private void AddQueryReceived(QueryReceivedEventHandler value)
+        {
+            using (Logger.CreateCallstackAndLock())
+            {
+                bool send = (_queryReceived == null);
+                _queryReceived += value;
+                if (Opened && send)
+                {
+                    SendOptionBatchCommand();
+                }
+            }
+        }
+
+        private void RemoveQueryReceived(QueryReceivedEventHandler value)
+        {
+            using (Logger.CreateCallstackAndLock())
+            {
+                if (_queryReceived != null)
+                {
+                    _queryReceived -= value;
+                    if (Opened && (_queryReceived == null))
+                    {
+                        SendOptionBatchCommand();
+                    }
+                }
+            }
+        }
+
         public Session()
         {
             Logger = new Logger();
@@ -127,6 +158,7 @@ namespace WinSCP
                 _operationResults = new List<OperationResultBase>();
                 _events = new List<Action>();
                 _eventsEvent = new AutoResetEvent(false);
+                _choiceEvent = new ManualResetEvent(false);
                 _disposed = false;
                 _defaultConfiguration = true;
                 _logUnique = 0;
@@ -156,6 +188,12 @@ namespace WinSCP
                     _eventsEvent = null;
                 }
 
+                if (_choiceEvent != null)
+                {
+                    _choiceEvent.Close();
+                    _choiceEvent = null;
+                }
+
                 GC.SuppressFinalize(this);
             }
         }
@@ -206,7 +244,7 @@ namespace WinSCP
                     GotOutput();
 
                     // setup batch mode
-                    WriteCommand("option batch on");
+                    SendOptionBatchCommand();
                     WriteCommand("option confirm off");
 
                     object reconnectTimeValue;
@@ -318,6 +356,12 @@ namespace WinSCP
             }
         }
 
+        private void SendOptionBatchCommand()
+        {
+            string command = string.Format(CultureInfo.InvariantCulture, "option batch {0}", (_queryReceived != null ? "off" : "on"));
+            WriteCommand(command);
+        }
+
         internal string GetErrorOutputMessage()
         {
             string result = null;
@@ -1821,6 +1865,22 @@ namespace WinSCP
             }
         }
 
+        internal void ProcessChoice(QueryReceivedEventArgs args)
+        {
+            if (_queryReceived != null) // optimization
+            {
+                _choiceEvent.Reset();
+                ScheduleEvent(() => Choice(args));
+                _choiceEvent.WaitOne();
+            }
+        }
+
+        private void Choice(QueryReceivedEventArgs args)
+        {
+            _queryReceived?.Invoke(this, args);
+            _choiceEvent.Set();
+        }
+
         internal void ProcessProgress(FileTransferProgressEventArgs args)
         {
             ScheduleEvent(() => Progress(args));
@@ -2161,6 +2221,7 @@ namespace WinSCP
         private delegate void Action();
         private readonly IList<Action> _events;
         private AutoResetEvent _eventsEvent;
+        private ManualResetEvent _choiceEvent;
         private bool _disposed;
         private string _executablePath;
         private string _additionalExecutableArguments;
@@ -2181,5 +2242,6 @@ namespace WinSCP
         private StringCollection _error;
         private bool _ignoreFailed;
         private TimeSpan _sessionTimeout;
+        private QueryReceivedEventHandler _queryReceived;
     }
 }

+ 1 - 0
dotnet/WinSCPnet.csproj

@@ -71,6 +71,7 @@
     <Compile Include="Internal\UnsafeNativeMethods.cs" />
     <Compile Include="OutputDataReceivedEventArgs.cs" />
     <Compile Include="FileTransferProgressEventArgs.cs" />
+    <Compile Include="QueryReceivedEventArgs.cs" />
     <Compile Include="RemoteDirectoryInfo.cs" />
     <Compile Include="FailedEventArgs.cs" />
     <Compile Include="FileOperationEventArgs.cs" />

+ 4 - 1
dotnet/internal/ConsoleCommStruct.cs

@@ -49,6 +49,9 @@ namespace WinSCP
         public uint Timer; // since version 2
         [MarshalAs(UnmanagedType.I1)]
         public bool Timeouting; // since version 4
+        public int Continue; // since version 9
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5120)]
+        public string Message; // since version 9
     }
 
     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
@@ -92,7 +95,7 @@ namespace WinSCP
 
     internal class ConsoleCommStruct : IDisposable
     {
-        public const int CurrentVersion = 0x0008;
+        public const int CurrentVersion = 0x0009;
 
         public ConsoleCommStruct(Session session, SafeFileHandle fileMapping)
         {

+ 35 - 7
dotnet/internal/ExeSessionProcess.cs

@@ -330,19 +330,47 @@ namespace WinSCP
         {
             using (_logger.CreateCallstack())
             {
-                if (e.Timeouting)
+                _logger.WriteLine(
+                    "Options: [{0}], Timer: [{1}], Timeouting: [{2}], Timeouted: [{3}], Break: [{4}]",
+                    e.Options, e.Timer, e.Timeouting, e.Timeouted, e.Break);
+
+                QueryReceivedEventArgs args = new QueryReceivedEventArgs
+                {
+                    Message = e.Message
+                };
+
+                _session.ProcessChoice(args);
+
+                if (args.SelectedAction == QueryReceivedEventArgs.Action.None)
+                {
+                    if (e.Timeouting)
+                    {
+                        Thread.Sleep((int)e.Timer);
+                        e.Result = e.Timeouted;
+                    }
+                    else
+                    {
+                        e.Result = e.Break;
+                    }
+                }
+                else if (args.SelectedAction == QueryReceivedEventArgs.Action.Continue)
                 {
-                    Thread.Sleep((int)e.Timer);
-                    e.Result = e.Timeouted;
+                    if (e.Timeouting)
+                    {
+                        Thread.Sleep((int)e.Timer);
+                        e.Result = e.Timeouted;
+                    }
+                    else
+                    {
+                        e.Result = e.Continue;
+                    }
                 }
-                else
+                else if (args.SelectedAction == QueryReceivedEventArgs.Action.Abort)
                 {
                     e.Result = e.Break;
                 }
 
-                _logger.WriteLine(
-                    "Options: [{0}], Timer: [{1}], Timeouting: [{2}], Timeouted: [{3}], Break: [{4}], Result: [{5}]",
-                    e.Options, e.Timer, e.Timeouting, e.Timeouted, e.Break, e.Result);
+                _logger.WriteLine("Options Result: [{0}]", e.Result);
             }
         }
 

+ 3 - 4
source/Console.cbproj

@@ -43,11 +43,10 @@
 		<ProjectType>CppConsoleApplication</ProjectType>
 		<SanitizedProjectName>Console</SanitizedProjectName>
 		<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=4.5.2.0;InternalName=console;LegalCopyright=(c) 2000-2018 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.12.1.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
+		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=5.0.0.0;InternalName=console;LegalCopyright=(c) 2000-2018 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.12.1.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
 		<VerInfo_Locale>1033</VerInfo_Locale>
-		<VerInfo_MajorVer>4</VerInfo_MajorVer>
-		<VerInfo_MinorVer>5</VerInfo_MinorVer>
-		<VerInfo_Release>2</VerInfo_Release>
+		<VerInfo_MajorVer>5</VerInfo_MajorVer>
+		<VerInfo_MinorVer>0</VerInfo_MinorVer>
 	</PropertyGroup>
 	<PropertyGroup Condition="'$(Cfg_1)'!=''">
 		<BCC_DebugLineNumbers>true</BCC_DebugLineNumbers>

+ 5 - 2
source/console/Console.h

@@ -12,8 +12,8 @@ struct TConsoleCommStruct
 {
   enum TVersion
   {
-    CurrentVersion =          0x0008,
-    CurrentVersionConfirmed = 0x0108
+    CurrentVersion =          0x0009,
+    CurrentVersionConfirmed = 0x0109
   };
 
   struct TInitEvent
@@ -47,6 +47,9 @@ struct TConsoleCommStruct
     int Timeouted; // since version 2
     unsigned int Timer; // since version 2
     bool Timeouting; // since version 4
+    int Continue; // since version 9
+    unsigned int Timeout; // since version 9
+    wchar_t Message[5120]; // since version 9
   };
 
   struct TTitleEvent

+ 68 - 18
source/windows/ConsoleRunner.cpp

@@ -45,8 +45,9 @@ public:
   virtual __fastcall ~TConsole() {};
   virtual void __fastcall Print(UnicodeString Str, bool FromBeginning = false, bool Error = false) = 0;
   virtual bool __fastcall Input(UnicodeString & Str, bool Echo, unsigned int Timer) = 0;
-  virtual int __fastcall Choice(UnicodeString Options, int Cancel, int Break,
-    int Timeouted, bool Timeouting, unsigned int Timer) = 0;
+  virtual int __fastcall Choice(
+    UnicodeString Options, int Cancel, int Break, int Continue, int Timeouted, bool Timeouting, unsigned int Timer,
+    UnicodeString Message) = 0;
   virtual bool __fastcall PendingAbort() = 0;
   virtual void __fastcall SetTitle(UnicodeString Title) = 0;
   virtual bool __fastcall LimitedOutput() = 0;
@@ -66,8 +67,9 @@ public:
 
   virtual void __fastcall Print(UnicodeString Str, bool FromBeginning = false, bool Error = false);
   virtual bool __fastcall Input(UnicodeString & Str, bool Echo, unsigned int Timer);
-  virtual int __fastcall Choice(UnicodeString Options, int Cancel, int Break,
-    int Timeouted, bool Timeouting, unsigned int Timer);
+  virtual int __fastcall Choice(
+    UnicodeString Options, int Cancel, int Break, int Continue, int Timeouted, bool Timeouting, unsigned int Timer,
+    UnicodeString Message);
   virtual bool __fastcall PendingAbort();
   virtual void __fastcall SetTitle(UnicodeString Title);
   virtual bool __fastcall LimitedOutput();
@@ -394,8 +396,9 @@ bool __fastcall TOwnConsole::Input(UnicodeString & Str, bool Echo, unsigned int
   return Result;
 }
 //---------------------------------------------------------------------------
-int __fastcall TOwnConsole::Choice(UnicodeString Options, int Cancel, int Break,
-  int Timeouted, bool /*Timeouting*/, unsigned int Timer)
+int __fastcall TOwnConsole::Choice(
+  UnicodeString Options, int Cancel, int Break, int /*Continue*/, int Timeouted, bool /*Timeouting*/, unsigned int Timer,
+  UnicodeString Message)
 {
   unsigned int ATimer = Timer;
   int Result = 0;
@@ -537,8 +540,9 @@ public:
 
   virtual void __fastcall Print(UnicodeString Str, bool FromBeginning = false, bool Error = false);
   virtual bool __fastcall Input(UnicodeString & Str, bool Echo, unsigned int Timer);
-  virtual int __fastcall Choice(UnicodeString Options, int Cancel, int Break,
-    int Timeouted, bool Timeouting, unsigned int Timer);
+  virtual int __fastcall Choice(
+    UnicodeString Options, int Cancel, int Break, int Continue, int Timeouted, bool Timeouting, unsigned int Timer,
+    UnicodeString Message);
   virtual bool __fastcall PendingAbort();
   virtual void __fastcall SetTitle(UnicodeString Title);
   virtual bool __fastcall LimitedOutput();
@@ -753,8 +757,9 @@ bool __fastcall TExternalConsole::Input(UnicodeString & Str, bool Echo, unsigned
   return Result;
 }
 //---------------------------------------------------------------------------
-int __fastcall TExternalConsole::Choice(UnicodeString Options, int Cancel, int Break,
-  int Timeouted, bool Timeouting, unsigned int Timer)
+int __fastcall TExternalConsole::Choice(
+  UnicodeString Options, int Cancel, int Break, int Continue, int Timeouted, bool Timeouting, unsigned int Timer,
+  UnicodeString Message)
 {
   TConsoleCommStruct * CommStruct = GetCommStruct();
   try
@@ -766,9 +771,13 @@ int __fastcall TExternalConsole::Choice(UnicodeString Options, int Cancel, int B
     CommStruct->ChoiceEvent.Cancel = Cancel;
     CommStruct->ChoiceEvent.Break = Break;
     CommStruct->ChoiceEvent.Result = Break;
+    CommStruct->ChoiceEvent.Continue = Continue;
     CommStruct->ChoiceEvent.Timeouted = Timeouted;
     CommStruct->ChoiceEvent.Timer = Timer;
     CommStruct->ChoiceEvent.Timeouting = Timeouting;
+    size_t MaxLen = LENOF(CommStruct->ChoiceEvent.Message) - 1;
+    Message = Message.SubString(1, MaxLen);
+    wcscpy(CommStruct->ChoiceEvent.Message, Message.c_str());
   }
   __finally
   {
@@ -946,8 +955,9 @@ public:
 
   virtual void __fastcall Print(UnicodeString Str, bool FromBeginning = false, bool Error = false);
   virtual bool __fastcall Input(UnicodeString & Str, bool Echo, unsigned int Timer);
-  virtual int __fastcall Choice(UnicodeString Options, int Cancel, int Break,
-    int Timeouted, bool Timeouting, unsigned int Timer);
+  virtual int __fastcall Choice(
+    UnicodeString Options, int Cancel, int Break, int Continue, int Timeouted, bool Timeouting, unsigned int Timer,
+    UnicodeString Message);
   virtual bool __fastcall PendingAbort();
   virtual void __fastcall SetTitle(UnicodeString Title);
   virtual bool __fastcall LimitedOutput();
@@ -976,8 +986,9 @@ bool __fastcall TNullConsole::Input(UnicodeString & /*Str*/, bool /*Echo*/,
   return false;
 }
 //---------------------------------------------------------------------------
-int __fastcall TNullConsole::Choice(UnicodeString /*Options*/, int /*Cancel*/,
-  int Break, int Timeouted, bool Timeouting, unsigned int Timer)
+int __fastcall TNullConsole::Choice(
+  UnicodeString /*Options*/, int /*Cancel*/, int Break, int /*Continue*/, int Timeouted, bool Timeouting,
+  unsigned int Timer, UnicodeString /*Message*/)
 {
   int Result;
   if (Timeouting)
@@ -1369,10 +1380,12 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
 
   unsigned int AAnswers = Answers;
 
+  UnicodeString Message = AQuery;
   PrintMessage(AQuery);
   if ((MoreMessages != NULL) && (MoreMessages->Count > 0))
   {
     PrintMessage(MoreMessages->Text);
+    Message += L"\n" + MoreMessages->Text;
   }
 
   std::vector<unsigned int> Buttons;
@@ -1577,10 +1590,47 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
       }
       else
       {
-        unsigned int ActualTimer =
-          (Timeouting && ((Timer == 0) || (Timer > MSecsPerSec)) ? MSecsPerSec : Timer);
-        AnswerIndex = FConsole->Choice(Accels, CancelIndex, -1, -2,
-          Timeouting, ActualTimer);
+        unsigned int ActualTimer;
+        if (Timeouting)
+        {
+          if (Timer == 0)
+          {
+            if (FConsole->NoInteractiveInput())
+            {
+              ActualTimer = Timeout;
+            }
+            else
+            {
+              ActualTimer = MSecsPerSec;
+            }
+          }
+          else
+          {
+            if (Timer < MSecsPerSec)
+            {
+              ActualTimer = Timer;
+            }
+            else
+            {
+              ActualTimer = MSecsPerSec;
+            }
+          }
+        }
+        else
+        {
+          ActualTimer = Timer;
+        }
+        // Not to get preliminary "host is not responding" messages to .NET assembly
+        if (FConsole->NoInteractiveInput() && (Timer > 0))
+        {
+          Sleep(Timer);
+          AnswerIndex = -2;
+        }
+        else
+        {
+          AnswerIndex =
+            FConsole->Choice(Accels, CancelIndex, -1, ContinueIndex, -2, Timeouting, ActualTimer, Message);
+        }
         if (AnswerIndex == -1)
         {
           NotifyAbort();