fix: null ref checks in SmtpEmailSender

fix: null ref checks in SmtpEmailSender

--- src/ServiceBase.Notification.Smtp/SmtpEmailSender.cs
+++ src/ServiceBase.Notification.Smtp/SmtpEmailSender.cs
@@ -34,11 +34,12 @@
         {
             var hasText = !string.IsNullOrWhiteSpace(message.Text);
             var hasHtml = !string.IsNullOrWhiteSpace(message.Html);
+            var from = message.EmailFrom ?? this._options.EmailFrom;

             // validate message
             if ((!hasText && !hasHtml) ||
                 string.IsNullOrWhiteSpace(message.Subject) ||
-                string.IsNullOrWhiteSpace(message.EmailFrom) ||
+                string.IsNullOrWhiteSpace(from) ||
                 !message.EmailTos.Any())
             {
                 this._logger.LogError("Invalid EmailMessage");
@@ -59,7 +60,7 @@
             {
                 using (var mail = new MailMessage())
                 {
-                    mail.From = new MailAddress(message.EmailFrom);
+                    mail.From = new MailAddress(from);
                     mail.Subject = message.Subject;
                     mail.BodyEncoding = Encoding.UTF8;
                     mail.SubjectEncoding = Encoding.UTF8;
@@ -69,14 +70,20 @@
                         mail.To.Add(new MailAddress(item));
                     }

-                    foreach (var item in message.EmailCcs)
+                    if (message.EmailCcs != null)
                     {
-                        mail.CC.Add(new MailAddress(item));
+                        foreach (var item in message.EmailCcs)
+                        {
+                            mail.CC.Add(new MailAddress(item));
+                        }
                     }

-                    foreach (var item in message.EmailBccs)
+                    if (message.EmailBccs != null)
                     {
-                        mail.Bcc.Add(new MailAddress(item));
+                        foreach (var item in message.EmailBccs)
+                        {
+                            mail.Bcc.Add(new MailAddress(item));
+                        }
                     }

                     // has only text


Added debug logging to smtp mail sender

Added debug logging to smtp mail sender

--- src/ServiceBase.Notification.Smtp/SmtpEmailSender.cs
+++ src/ServiceBase.Notification.Smtp/SmtpEmailSender.cs
@@ -3,6 +3,7 @@

 namespace ServiceBase.Notification.Smtp
 {
+    using System.Linq;
     using System.Net;
     using System.Net.Mail;
     using System.Net.Mime;
@@ -10,7 +11,6 @@
     using System.Threading.Tasks;
     using Microsoft.Extensions.Logging;
     using ServiceBase.Notification.Email;
-    using System.Linq;

     public class SmtpEmailSender : IEmailSender
     {
@@ -32,9 +32,9 @@
         /// <returns></returns>
         public async Task SendEmailAsync(EmailMessage message)
         {
-            var hasText = !string.IsNullOrWhiteSpace(message.Text);
-            var hasHtml = !string.IsNullOrWhiteSpace(message.Html);
-            var from = message.EmailFrom ?? this._options.EmailFrom;
+            bool hasText = !string.IsNullOrWhiteSpace(message.Text);
+            bool hasHtml = !string.IsNullOrWhiteSpace(message.Html);
+            string from = message.EmailFrom ?? this._options.EmailFrom;

             // validate message
             if ((!hasText && !hasHtml) ||
@@ -46,7 +46,7 @@
                 return;
             }

-            using (var client = new SmtpClient()
+            using var client = new SmtpClient()
             {
                 Host = this._options.Host,
                 Port = this._options.Port,
@@ -56,68 +56,68 @@
                 this._options.UserName,
                 this._options.Password
             )
-            })
+            };
+
+            using var mail = new MailMessage();
+
+            mail.From = new MailAddress(from);
+            mail.Subject = message.Subject;
+            mail.BodyEncoding = Encoding.UTF8;
+            mail.SubjectEncoding = Encoding.UTF8;
+
+            foreach (var item in message.EmailTos)
             {
-                using (var mail = new MailMessage())
+                mail.To.Add(new MailAddress(item));
+            }
+
+            if (message.EmailCcs != null)
+            {
+                foreach (var item in message.EmailCcs)
                 {
-                    mail.From = new MailAddress(from);
-                    mail.Subject = message.Subject;
-                    mail.BodyEncoding = Encoding.UTF8;
-                    mail.SubjectEncoding = Encoding.UTF8;
+                    mail.CC.Add(new MailAddress(item));
+                }
+            }

-                    foreach (var item in message.EmailTos)
-                    {
-                        mail.To.Add(new MailAddress(item));
-                    }
+            if (message.EmailBccs != null)
+            {
+                foreach (var item in message.EmailBccs)
+                {
+                    mail.Bcc.Add(new MailAddress(item));
+                }
+            }

-                    if (message.EmailCcs != null)
-                    {
-                        foreach (var item in message.EmailCcs)
-                        {
-                            mail.CC.Add(new MailAddress(item));
-                        }
-                    }
+            // has only text
+            if (hasText && !hasHtml)
+            {
+                mail.Body = message.Text;
+            }
+            // has only html
+            else if (hasHtml && !hasText)
+            {
+                mail.Body = message.Html;
+                mail.IsBodyHtml = true;
+            }
+            // has both
+            else
+            {
+                mail.Body = message.Text;

-                    if (message.EmailBccs != null)
-                    {
-                        foreach (var item in message.EmailBccs)
-                        {
-                            mail.Bcc.Add(new MailAddress(item));
-                        }
-                    }
+                AlternateView htmlView = AlternateView
+                  .CreateAlternateViewFromString(message.Html);

-                    // has only text
-                    if (hasText && !hasHtml)
-                    {
-                        mail.Body = message.Text;
-                    }
-                    // has only html
-                    else if (hasHtml && !hasText)
-                    {
-                        mail.Body = message.Html;
-                        mail.IsBodyHtml = true;
-                    }
-                    // has both
-                    else
-                    {
-                        mail.Body = message.Text;
+                htmlView.ContentType = new ContentType("text/html");
+                mail.AlternateViews.Add(htmlView);
+            }

-                        AlternateView htmlView = AlternateView
-                          .CreateAlternateViewFromString(message.Html);
-
-                        htmlView.ContentType = new ContentType("text/html");
-                        mail.AlternateViews.Add(htmlView);
-                    }
-
-                    try
-                    {
-                        client.Send(mail);
-                    }
-                    catch (System.Exception ex)
-                    {
-                        this._logger.LogError(ex);
-                    }
-                }
+            try
+            {
+                this._logger.LogDebug("Sending email via SMTP sender");
+                client.Send(mail);
+            }
+            catch (System.Exception ex)
+            {
+                this._logger.LogError(ex,
+                    "Error while sending mail message via SMTP sender");
             }
         }
     }


Updated reference OData services URLs.

Updated reference OData services URLs.

--- src/Simple.OData.Client.IntegrationTests/TestBase.cs
+++ src/Simple.OData.Client.IntegrationTests/TestBase.cs
@@ -9,17 +9,17 @@
 {
     public abstract class TestBase : IDisposable
     {
-        protected const string ODataV2ReadWriteUri = "http://services.odata.org/V2/%28S%28readwrite%29%29/OData/OData.svc/";
-        protected const string ODataV3ReadOnlyUri = "http://services.odata.org/V3/OData/OData.svc/";
-        protected const string ODataV3ReadWriteUri = "http://services.odata.org/V3/%28S%28readwrite%29%29/OData/OData.svc/";
-        protected const string ODataV4ReadOnlyUri = "http://services.odata.org/V4/OData/OData.svc/";
-        protected const string ODataV4ReadWriteUri = "http://services.odata.org/V4/OData/%28S%28readwrite%29%29/OData.svc/";
+        protected const string ODataV2ReadWriteUri = "https://services.odata.org/V2/%28S%28readwrite%29%29/OData/OData.svc/";
+        protected const string ODataV3ReadOnlyUri = "https://services.odata.org/V3/OData/OData.svc/";
+        protected const string ODataV3ReadWriteUri = "https://services.odata.org/V3/%28S%28readwrite%29%29/OData/OData.svc/";
+        protected const string ODataV4ReadOnlyUri = "https://services.odata.org/V4/OData/OData.svc/";
+        protected const string ODataV4ReadWriteUri = "https://services.odata.org/V4/OData/%28S%28readwrite%29%29/OData.svc/";

-        protected const string NorthwindV2ReadOnlyUri = "http://services.odata.org/V2/Northwind/Northwind.svc/";
-        protected const string NorthwindV3ReadOnlyUri = "http://services.odata.org/V3/Northwind/Northwind.svc/";
-        protected const string NorthwindV4ReadOnlyUri = "http://services.odata.org/V4/Northwind/Northwind.svc/";
+        protected const string NorthwindV2ReadOnlyUri = "https://services.odata.org/V2/Northwind/Northwind.svc/";
+        protected const string NorthwindV3ReadOnlyUri = "https://services.odata.org/V3/Northwind/Northwind.svc/";
+        protected const string NorthwindV4ReadOnlyUri = "https://services.odata.org/V4/Northwind/Northwind.svc/";

-        protected const string TripPinV4ReadWriteUri = "http://services.odata.org/V4/TripPinServiceRW/";
+        protected const string TripPinV4ReadWriteUri = "https://services.odata.org/V4/TripPinServiceRW/";

         protected readonly Uri _serviceUri;
         protected readonly ODataPayloadFormat _payloadFormat;


Removed extra colon at the end of some SNI error messages (was causing the stress test to fail to recognize these errors).

--- src/SqlClientSlim/System/Data/SqlClient/TdsParser.cs
+++ src/SqlClientSlim/System/Data/SqlClient/TdsParser.cs
@@ -1130,36 +1130,18 @@
             }
             else
             {
-#if MANAGED_SNI
                 // SNI error. Append additional error message info if available.
                 //
                 string sniLookupMessage = SQL.GetSNIErrorMessage((int)sniError.sniError);
-                errorMessage =  (sniError.errorMessage != null) ?
+                errorMessage =  !string.IsNullOrEmpty(sniError.errorMessage) ?
                                 (sniLookupMessage + ": " + sniError.errorMessage) :
                                 sniLookupMessage;
-#else
-                // SNI error. Replace the entire message.
-                //
-                errorMessage = SQL.GetSNIErrorMessage((int)sniError.sniError);
-
-                // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
-                if (sniError.sniError == (int)SNINativeMethodWrapper.SniSpecialErrors.LocalDBErrorCode)
-                {
-                    errorMessage += LocalDBAPI.GetLocalDBMessage((int)sniError.nativeError);
-                    win32ErrorCode = 0;
-                }
-#endif // MANAGED_SNI
             }
             errorMessage = String.Format((IFormatProvider)null, "{0} (provider: {1}, error: {2} - {3})",
                 sqlContextInfo, providerName, (int)sniError.sniError, errorMessage);

-#if MANAGED_SNI
             return new SqlError((int)sniError.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
                                 _server, errorMessage, sniError.function, (int)sniError.lineNumber, win32ErrorCode, sniError.exception);
-#else
-            return new SqlError((int)sniError.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
-                    _server, errorMessage, sniError.function, (int)sniError.lineNumber, win32ErrorCode);
-#endif
         }

         internal void CheckResetConnection(TdsParserStateObject stateObj)


Test that are failing

Test that are failing

Unit test that are failing due to the deserialization of the multi field
map

--- src/Tests/Nest.Tests.Integration/Mapping/MapTests.cs
+++ src/Tests/Nest.Tests.Integration/Mapping/MapTests.cs
@@ -115,6 +115,44 @@
 				var typeMapping = this.Client.GetMapping<ElasticsearchProject>(gm => gm.Index("asdasdasdsada").Type("elasticsearchprojects2")).Mapping;
 			});
 		}
+
+		[Test]
+		public void GetAndUpdateMultiFieldMap()
+		{
+			var indexName = ElasticsearchConfiguration.NewUniqueIndexName();

+			var indexCreateResponse = this.Client.CreateIndex(indexName);
+			Assert.IsTrue(indexCreateResponse.Acknowledged, indexCreateResponse.ConnectionStatus.ToString());
+
+			var mapResponse = this.Client.Map<ElasticsearchProject>(m => m
+				.Index(indexName)
+				.Properties(props => props
+					.MultiField(s => s
+						.Name(p => p.Name)
+						.Fields(pprops => pprops
+							.String(ps => ps.Name(p => p.Name).Index(FieldIndexOption.NotAnalyzed))
+							.String(ps => ps.Name(p => p.Name.Suffix("searchable")).Index(FieldIndexOption.Analyzed))
+						)
+					)
+				)
+				);
+			mapResponse.Should().NotBeNull();
+			Assert.IsTrue(mapResponse.IsValid, mapResponse.ConnectionStatus.ToString());
+
+			var getMapResponse = this.Client.GetMapping<ElasticsearchProject>(m => m.Index(indexName));
+			getMapResponse.Should().NotBeNull();
+			Assert.IsTrue(getMapResponse.IsValid, getMapResponse.ConnectionStatus.ToString());
+			getMapResponse.Mapping.Should().NotBeNull();
+
+			mapResponse = this.Client.Map<ElasticsearchProject>(p => p
+				.Index(indexName)
+				.InitializeUsing(getMapResponse.Mapping)
+				);
+			mapResponse.Should().NotBeNull();
+			Assert.IsTrue(mapResponse.IsValid, mapResponse.ConnectionStatus.ToString());
+
+			var indexDeleteResponse = this.Client.DeleteIndex(x => x.Index(indexName));
+			Assert.IsTrue(indexDeleteResponse.Acknowledged, indexDeleteResponse.ConnectionStatus.ToString());
+		}
 	}
 }


change build command handler to unsubscribe after executing command.

--- Dewey.Build/BuildCommandHandler.cs
+++ Dewey.Build/BuildCommandHandler.cs
@@ -4,7 +4,6 @@
 using Dewey.Manifest.Models;
 using Dewey.Messaging;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;

@@ -28,8 +27,6 @@
             _eventAggregator = eventAggregator;
             _buildActionFactory = buildActionFactory;
             _buildCommandCache = buildCommandCache;
-
-            eventAggregator.Subscribe(this);
         }

         public void Execute(BuildCommand command)
@@ -43,9 +40,11 @@
             }
             _eventAggregator.PublishEvent(new BuildCommandStarted(command));

+            _eventAggregator.Subscribe(this);
             var stopwatch = Stopwatch.StartNew();
             var result = Execute();
             stopwatch.Stop();
+            _eventAggregator.Unsubscribe(this);

             _eventAggregator.PublishEvent(new BuildCommandCompleted(command, result, stopwatch.Elapsed));
         }


buttonpress of hapring

buttonpress of hapring

--- unityproject/app/Assets/scripts/HapringController.cs
+++ unityproject/app/Assets/scripts/HapringController.cs
@@ -37,6 +37,9 @@

     void switchNode(Direction direction)
     {
+		if (direction == Direction.up) {
+			chooseSelectedNode();
+		}
         if (direction == Direction.right)
         {
             nodes = HighlightNode.GetAllHighlighted();


Don't close contentStream's stream.

Don't close contentStream's stream.

The defined delegate will be used inside a loop, which can
causes crashes, because the stream gets closed multiple times.

It's the users responsibility anyway.

--- DotCMIS/binding/atompub/atompub.cs
+++ DotCMIS/binding/atompub/atompub.cs
@@ -2083,8 +2083,7 @@
                 {
                     stream.Write(buffer, 0, b);
                 }
-
-                contentStream.Stream.Close();
+                // user is responsible for closing the stream
             };

             IDictionary<string, string> headers = null;
@@ -2171,8 +2170,7 @@
                 {
                     stream.Write(buffer, 0, b);
                 }
-
-                contentStream.Stream.Close();
+                // user is responsible for closing the stream
             };

             IDictionary<string, string> headers = null;


Workaround for Documentum enabled

Workaround for Documentum enabled

--- DotCMIS/binding/atompub/atompub.cs
+++ DotCMIS/binding/atompub/atompub.cs
@@ -436,14 +436,14 @@

             if (resp.StatusCode != HttpStatusCode.OK)
             {
-//                // Workaround for Documentum, see https://github.com/aegif/CmisSync/issues/607
-//                string documentumWorkaroundUrl = url.ToString().Replace("cmis:path,", "");
-//                resp = HttpUtils.InvokeGET(new UrlBuilder(documentumWorkaroundUrl), Session);
-//
-//                if (resp.StatusCode != HttpStatusCode.OK)
-//                {
+                // Workaround for Documentum, see https://github.com/aegif/CmisSync/issues/607
+                string documentumWorkaroundUrl = url.ToString().Replace("cmis:path,", "");
+                resp = HttpUtils.InvokeGET(new UrlBuilder(documentumWorkaroundUrl), Session);
+
+                if (resp.StatusCode != HttpStatusCode.OK)
+                {
                     throw ConvertToCmisException(resp);
-//                }
+                }
             }

             return resp;


initial import of transaction-around-copy

--- PalMon/Output/OutputDrivers/PostgresOutput.cs
+++ PalMon/Output/OutputDrivers/PostgresOutput.cs
@@ -69,6 +69,7 @@
             }

             ReconnectoToDbIfNeeded();
+
             var tableName = DBWriter.GetTableName(fileNames[0]);

             if (!tableCreators.ContainsKey(tableName))
@@ -88,38 +89,58 @@
             LoggingHelpers.TimedLog(Log, statusLine, () =>
             {
                 int rowsWritten = 0;
-                using (var writer = connection.BeginTextImport(copyString))
+                // begin a transaction before any insert takes place
+                var copyTransaction = connection.BeginTransaction();
+
+                // we wrap the copy in a try/catch block so we can roll back the transaction in case of errors
+                try
                 {
-                    // Files contents for the same table are sent in one bulk
-                    foreach (var fileName in fileNames)
+
+                    using (var writer = connection.BeginTextImport(copyString))
                     {
-                        if (!copyString.Equals(CopyStatementFor(fileName)))
+                        // Files contents for the same table are sent in one bulk
+                        foreach (var fileName in fileNames)
                         {
-                            Log.Error("Skipping file since CSV header is not matching with others in file: {0}", fileName);
-                            continue;
-                        }
-
-                        using (var reader = new StreamReader(fileName))
-                        {
-                            // Skip the first line of the CSV file as it only contains the CSV header
-                            var lineRead = reader.ReadLine();
-
-                            while (true)
+                            if (!copyString.Equals(CopyStatementFor(fileName)))
                             {
-                                lineRead = reader.ReadLine();
-                                if (lineRead == null)
+                                Log.Error("Skipping file since CSV header is not matching with others in file: {0}", fileName);
+                                continue;
+                            }
+
+                            using (var reader = new StreamReader(fileName))
+                            {
+                                // Skip the first line of the CSV file as it only contains the CSV header
+                                var lineRead = reader.ReadLine();
+
+                                while (true)
                                 {
-                                    // End of file
-                                    break;
+                                    lineRead = reader.ReadLine();
+                                    if (lineRead == null)
+                                    {
+                                        // End of file
+                                        break;
+                                    }
+                                    rowsWritten++;
+                                    writer.WriteLine(lineRead);
                                 }
-                                rowsWritten++;
-                                writer.WriteLine(lineRead);
                             }
                         }
                     }
+                    // commit the transaction after all rows are written (after writer.Dispose() is called)
+                    copyTransaction.Commit();
+
+                    return rowsWritten;
                 }
-                return rowsWritten;
+                catch (Exception e)
+                {
+                    Log.Error(e, "Error during writing to the database: {0}", e);
+                    // if anything went wrong, we sh
+                    if (copyTransaction != null) copyTransaction.Rollback();
+                    // in case of errors we have inserted 0 rows thanks to the transaction
+                    return 0;
+                }
             });
+
         }

         /// <summary>


Updated copyright info

Updated copyright info

--- DotNetAsm/AsmCommandLineOptions.cs
+++ DotNetAsm/AsmCommandLineOptions.cs
@@ -1,23 +1,8 @@
 //-----------------------------------------------------------------------------
 // Copyright (c) 2017, 2018 informedcitizenry <informedcitizenry@gmail.com>
 //
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
+// Licensed under the MIT license. See LICENSE for full license information.
 //
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
 //-----------------------------------------------------------------------------

 using System;


C# 6.0 features.

C# 6.0 features.

--- Connectors/Quik/QuikOrderCondition.cs
+++ Connectors/Quik/QuikOrderCondition.cs
@@ -181,8 +181,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1691Key)]
 		public QuikOrderConditionTypes? Type
 		{
-			get { return (QuikOrderConditionTypes?)Parameters.TryGetValue("Type"); }
-			set { Parameters["Type"] = value; }
+			get { return (QuikOrderConditionTypes?)Parameters.TryGetValue(nameof(Type)); }
+			set { Parameters[nameof(Type)] = value; }
 		}

 		///<summary>
@@ -197,8 +197,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1739Key)]
 		public QuikOrderConditionResults? Result
 		{
-			get { return (QuikOrderConditionResults?)Parameters.TryGetValue("Result"); }
-			set { Parameters["Result"] = value; }
+			get { return (QuikOrderConditionResults?)Parameters.TryGetValue(nameof(Result)); }
+			set { Parameters[nameof(Result)] = value; }
 		}

 		/// <summary>
@@ -210,8 +210,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1740Key)]
 		public SecurityId? OtherSecurityId
 		{
-			get { return (SecurityId?)Parameters.TryGetValue("OtherSecurityId"); }
-			set { Parameters["OtherSecurityId"] = value; }
+			get { return (SecurityId?)Parameters.TryGetValue(nameof(OtherSecurityId)); }
+			set { Parameters[nameof(OtherSecurityId)] = value; }
 		}

 		/// <summary>
@@ -223,8 +223,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1742Key)]
 		public QuikStopPriceConditions? StopPriceCondition
 		{
-			get { return (QuikStopPriceConditions?)Parameters.TryGetValue("StopPriceCondition"); }
-			set { Parameters["StopPriceCondition"] = value; }
+			get { return (QuikStopPriceConditions?)Parameters.TryGetValue(nameof(StopPriceCondition)); }
+			set { Parameters[nameof(StopPriceCondition)] = value; }
 		}

 		/// <summary>
@@ -238,8 +238,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1744Key)]
 		public decimal? StopPrice
 		{
-			get { return (decimal?)Parameters.TryGetValue("StopPrice"); }
-			set { Parameters["StopPrice"] = value; }
+			get { return (decimal?)Parameters.TryGetValue(nameof(StopPrice)); }
+			set { Parameters[nameof(StopPrice)] = value; }
 		}

 		/// <summary>
@@ -252,8 +252,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1746Key)]
 		public decimal? StopLimitPrice
 		{
-			get { return (decimal?)Parameters.TryGetValue("StopLimitPrice"); }
-			set { Parameters["StopLimitPrice"] = value; }
+			get { return (decimal?)Parameters.TryGetValue(nameof(StopLimitPrice)); }
+			set { Parameters[nameof(StopLimitPrice)] = value; }
 		}

 		/// <summary>
@@ -266,8 +266,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1748Key)]
 		public bool? IsMarketStopLimit
 		{
-			get { return (bool?)Parameters.TryGetValue("IsMarketStopLimit"); }
-			set { Parameters["IsMarketStopLimit"] = value; }
+			get { return (bool?)Parameters.TryGetValue(nameof(IsMarketStopLimit)); }
+			set { Parameters[nameof(IsMarketStopLimit)] = value; }
 		}

 		/// <summary>
@@ -280,8 +280,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1750Key)]
 		public Range<DateTimeOffset> ActiveTime
 		{
-			get { return (Range<DateTimeOffset>)Parameters.TryGetValue("ActiveTime"); }
-			set { Parameters["ActiveTime"] = value; }
+			get { return (Range<DateTimeOffset>)Parameters.TryGetValue(nameof(ActiveTime)); }
+			set { Parameters[nameof(ActiveTime)] = value; }
 		}

 		/// <summary>
@@ -292,8 +292,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1751Key, true)]
 		public long? ConditionOrderId
 		{
-			get { return (long?)Parameters.TryGetValue("ConditionOrderId"); }
-			set { Parameters["ConditionOrderId"] = value; }
+			get { return (long?)Parameters.TryGetValue(nameof(ConditionOrderId)); }
+			set { Parameters[nameof(ConditionOrderId)] = value; }
 		}

 		/// <summary>
@@ -304,8 +304,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1754Key)]
 		public Sides? ConditionOrderSide
 		{
-			get { return (Sides?)Parameters.TryGetValue("ConditionOrderSide"); }
-			set { Parameters["ConditionOrderSide"] = value; }
+			get { return (Sides?)Parameters.TryGetValue(nameof(ConditionOrderSide)); }
+			set { Parameters[nameof(ConditionOrderSide)] = value; }
 		}

 		/// <summary>
@@ -319,8 +319,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1756Key)]
 		public bool? ConditionOrderPartiallyMatched
 		{
-			get { return (bool?)Parameters.TryGetValue("ConditionOrderPartiallyMatched"); }
-			set { Parameters["ConditionOrderPartiallyMatched"] = value; }
+			get { return (bool?)Parameters.TryGetValue(nameof(ConditionOrderPartiallyMatched)); }
+			set { Parameters[nameof(ConditionOrderPartiallyMatched)] = value; }
 		}

 		/// <summary>
@@ -334,8 +334,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1758Key)]
 		public bool? ConditionOrderUseMatchedBalance
 		{
-			get { return (bool?)Parameters.TryGetValue("ConditionOrderUseMatchedBalance"); }
-			set { Parameters["ConditionOrderUseMatchedBalance"] = value; }
+			get { return (bool?)Parameters.TryGetValue(nameof(ConditionOrderUseMatchedBalance)); }
+			set { Parameters[nameof(ConditionOrderUseMatchedBalance)] = value; }
 		}

 		/// <summary>
@@ -348,8 +348,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1760Key)]
 		public decimal? LinkedOrderPrice
 		{
-			get { return (decimal?)Parameters.TryGetValue("LinkedOrderPrice"); }
-			set { Parameters["LinkedOrderPrice"] = value; }
+			get { return (decimal?)Parameters.TryGetValue(nameof(LinkedOrderPrice)); }
+			set { Parameters[nameof(LinkedOrderPrice)] = value; }
 		}

 		/// <summary>
@@ -361,8 +361,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1762Key)]
 		public bool? LinkedOrderCancel
 		{
-			get { return (bool?)Parameters.TryGetValue("LinkedOrderCancel"); }
-			set { Parameters["LinkedOrderCancel"] = value; }
+			get { return (bool?)Parameters.TryGetValue(nameof(LinkedOrderCancel)); }
+			set { Parameters[nameof(LinkedOrderCancel)] = value; }
 		}

 		/// <summary>
@@ -374,8 +374,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1764Key)]
 		public Unit Offset
 		{
-			get { return (Unit)Parameters.TryGetValue("Offset"); }
-			set { Parameters["Offset"] = value; }
+			get { return (Unit)Parameters.TryGetValue(nameof(Offset)); }
+			set { Parameters[nameof(Offset)] = value; }
 		}

 		/// <summary>
@@ -387,8 +387,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1766Key)]
 		public Unit Spread
 		{
-			get { return (Unit)Parameters.TryGetValue("Spread"); }
-			set { Parameters["Spread"] = value; }
+			get { return (Unit)Parameters.TryGetValue(nameof(Spread)); }
+			set { Parameters[nameof(Spread)] = value; }
 		}

 		/// <summary>
@@ -401,8 +401,8 @@
 		[DescriptionLoc(LocalizedStrings.Str1768Key)]
 		public bool? IsMarketTakeProfit
 		{
-			get { return (bool?)Parameters.TryGetValue("IsMarketTakeProfit"); }
-			set { Parameters["IsMarketTakeProfit"] = value; }
+			get { return (bool?)Parameters.TryGetValue(nameof(IsMarketTakeProfit)); }
+			set { Parameters[nameof(IsMarketTakeProfit)] = value; }
 		}

 		/// <summary>


Adding more notes on drivers

Adding more notes on drivers

--- Drivers/PersonPartDisplayDriver.cs
+++ Drivers/PersonPartDisplayDriver.cs
@@ -48,9 +48,10 @@
         public override IDisplayResult Edit(PersonPart personPart, BuildPartEditorContext context) =>
             // Something similar to the Display method happens: you have a shape helper with a shape name possibly and
             // a factory. For editing using Initialize is the best idea. It will instantiate a view model from a type
-            // given as a generic parameter. In the factory you will map the content part properties to the view model.
+            // given as a generic parameter.
             // There are helper methods to generate the shape type. GetEditorShapeType() in this case will generate
-            // "PersonPart_Edit".
+            // "PersonPart_Edit" by default but this can be overridden form the part's settings under the content
+            // type's settings on the admin. In the factory you will map the content part properties to the view model.
             Initialize<PersonPartViewModel>(GetEditorShapeType(context), model =>
             {
                 model.PersonPart = personPart;


readability

readability

--- CryptoTracker/Helpers/JSONhistoric.cs
+++ CryptoTracker/Helpers/JSONhistoric.cs
@@ -8,12 +8,12 @@
         internal int LinuxTime;
         internal string Date { get; set; }
         internal DateTime DateTime { get; set; }
-        internal float Low { get; set; }
-        internal float High { get; set; }
-        internal float Open { get; set; }
-        internal float Close { get; set; }
-        internal float Volumefrom { get; set; }
-        internal float Volumeto { get; set; }
+        internal float Low { get; set; } = 0;
+        internal float High { get; set; } = 0;
+        internal float Open { get; set; } = 0;
+        internal float Close { get; set; } = 0;
+        internal float Volumefrom { get; set; } = 0;
+        internal float Volumeto { get; set; } = 0;

         public static List<JSONhistoric> HandleHistoricJSON(JToken data) {

@@ -33,14 +33,7 @@

             var hist = new List<JSONhistoric>();
             for (int i = 0; i < limit; i++) {
-                hist.Add( new JSONhistoric() {
-                    Low         = 0,
-                    High        = 0,
-                    Open        = 0,
-                    Close       = 0,
-                    Volumefrom  = 0,
-                    Volumeto    = 0
-                } );
+                hist.Add( new JSONhistoric() );
             }
             return hist;
         }


Renaming Variable.GetAddress to Variable.GetMemoryAddress

--- CsScriptManaged/CsScripts/Variable.cs
+++ CsScriptManaged/CsScripts/Variable.cs
@@ -489,9 +489,8 @@
         }

         /// <summary>
-        /// Gets the pointer address.
-        /// </summary>
-        /// <exception cref="System.ArgumentException">Variable is not a pointer type, but ...</exception>
+        /// Gets the pointer address. If this variable is pointer, it will return its value, otherwise it will return variable memory address.
+        /// </summary>
         public ulong GetPointerAddress()
         {
             return codeType.IsPointer ? Data : Address;
@@ -500,7 +499,7 @@
         /// <summary>
         /// Gets the memory address where value of this Variable is stored.
         /// </summary>
-        public ulong GetAddress()
+        public ulong GetMemoryAddress()
         {
             return Address;
         }


Fixing pointer aritmetics and type casting on Variable class.

--- CsScriptManaged/CsScripts/Variable.cs
+++ CsScriptManaged/CsScripts/Variable.cs
@@ -150,7 +150,7 @@
             ulong moduleId;

             Context.Symbols.GetSymbolTypeIdWide(name, out typeId, out moduleId);
-            return new Variable(moduleId, typeId, offset, name);
+            return CastVariableToUserType(new Variable(moduleId, typeId, offset, name));
         }

         /// <summary>
@@ -394,6 +394,25 @@
         }

         /// <summary>
+        /// Gets the pointer address.
+        /// </summary>
+        /// <exception cref="System.ArgumentException">Variable is not a pointer type, but ...</exception>
+        public ulong GetPointerAddress()
+        {
+            if (typedData.Tag == SymTag.PointerType)
+            {
+                return typedData.Data;
+            }
+
+            if (typedData.Offset == 0)
+            {
+                throw new ArgumentException("Variable is not a pointer type, but " + typedData.Tag);
+            }
+
+            return typedData.Offset;
+        }
+
+        /// <summary>
         /// Gets the name of variable.
         /// </summary>
         public string GetName()
@@ -439,6 +458,36 @@
         internal Variable[] GetOriginalFields()
         {
             return fields.Value;
+        }
+
+        /// <summary>
+        /// Gets the array element.
+        /// </summary>
+        /// <param name="index">The index.</param>
+        public Variable GetArrayElement(int index)
+        {
+            var response = Context.Advanced.Request(DebugRequest.ExtTypedDataAnsi, new EXT_TYPED_DATA()
+            {
+                Operation = ExtTdop.GetArrayElement,
+                InData = typedData,
+                In64 = (ulong)index,
+            }).OutData;
+
+            return CastVariableToUserType(new Variable(response));
+        }
+
+        /// <summary>
+        /// Dereferences the pointer.
+        /// </summary>
+        /// <exception cref="System.ArgumentException">Variable is not a pointer type, but ...</exception>
+        public Variable DereferencePointer()
+        {
+            if (typedData.Tag != SymTag.PointerType)
+            {
+                throw new ArgumentException("Variable is not a pointer type, but " + typedData.Tag);
+            }
+
+            return GetArrayElement(0);
         }

         /// <summary>
@@ -446,18 +495,23 @@
         /// </summary>
         /// <param name="offset">The offset.</param>
         /// <returns>Computed variable that points to new address</returns>
-        /// <exception cref="System.ArgumentException">Variable is not a pointer type, but </exception>
+        /// <exception cref="System.ArgumentException">Variable is not a pointer type, but ...</exception>
         public Variable AdjustPointer(int offset)
         {
-            if (typedData.Tag != SymTag.PointerType)
-            {
-                throw new ArgumentException("Variable is not a pointer type, but " + typedData.Tag);
-            }
-
-            DEBUG_TYPED_DATA newTypedData = typedData;
-
-            newTypedData.Data += (ulong)offset;
-            return new Variable(newTypedData);
+            if (typedData.Tag == SymTag.PointerType)
+            {
+                return DereferencePointer().AdjustPointer(offset);
+            }
+
+            if (typedData.Offset != 0)
+            {
+                DEBUG_TYPED_DATA newTypedData = typedData;
+
+                newTypedData.Offset += (ulong)offset;
+                return new Variable(newTypedData);
+            }
+
+            throw new ArgumentException("Variable is not a pointer type, but " + typedData.Tag);
         }

         /// <summary>
@@ -467,7 +521,12 @@
         /// <returns>Computed variable that is of new type.</returns>
         public Variable CastAs(DType newType)
         {
-            return new Variable(newType.ModuleId, newType.TypeId, typedData.Offset, name);
+            if (typedData.Tag == SymTag.PointerType)
+            {
+                return DereferencePointer().CastAs(newType);
+            }
+
+            return CastVariableToUserType(new Variable(newType.ModuleId, newType.TypeId, typedData.Offset, name));
         }

         /// <summary>
@@ -477,6 +536,11 @@
         /// <returns>Computed variable that is of new type.</returns>
         public Variable CastAs(string newType)
         {
+            if (typedData.Tag == SymTag.PointerType)
+            {
+                return DereferencePointer().CastAs(newType);
+            }
+
             uint newTypeId = Context.Symbols.GetTypeIdWide(typedData.ModBase, newType);
             int moduleIndex = newType.IndexOf('!');
             ulong moduleId = typedData.ModBase;
@@ -489,7 +553,7 @@
                 Context.Symbols.GetModuleByModuleName(moduleName, 0, out index, out moduleId);
             }

-            return new Variable(moduleId, newTypeId, typedData.Offset, name);
+            return CastVariableToUserType(new Variable(moduleId, newTypeId, typedData.Offset, name));
         }

         /// <summary>
@@ -707,14 +771,7 @@

                 if (typedData.Tag == SymTag.PointerType || typedData.Tag == SymTag.ArrayType)
                 {
-                    var response = Context.Advanced.Request(DebugRequest.ExtTypedDataAnsi, new EXT_TYPED_DATA()
-                    {
-                        Operation = ExtTdop.GetArrayElement,
-                        InData = typedData,
-                        In64 = (ulong)index,
-                    }).OutData;
-
-                    result = new Variable(response);
+                    result = GetArrayElement(index);
                     return true;
                 }
             }
@@ -779,6 +836,23 @@
         {
             // TODO: See if it is complex type and try to get VTable
             return null;
+        }
+
+        /// <summary>
+        /// Gets field offset.
+        /// </summary>
+        public int GetFieldOffset(string fieldName)
+        {
+            try
+            {
+                int offset = (int)Context.Symbols.GetFieldOffsetWide(typedData.ModBase, typedData.TypeId, fieldName);
+
+                return offset;
+            }
+            catch (Exception)
+            {
+                return -1;
+            }
         }

         /// <summary>


Add app-insights only to production

Add app-insights only to production

--- CollAction/Startup.cs
+++ CollAction/Startup.cs
@@ -112,7 +112,8 @@
                 // Note: use yyyy-MM-dd HH:mm:ss notation. Don't specify to use the default value (2018-05-27 23:59:59)
                 options.FestivalEndDate = Configuration.GetValue<DateTime?>("FestivalEndDate");
             });
-            services.AddApplicationInsightsTelemetry(Configuration);
+            if (Environment.IsProduction())
+                services.AddApplicationInsightsTelemetry(Configuration);
         }

         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.


Change Countdown component

Change Countdown component

Allow the countdown component to be attached to any other component;
Added FadeOut handling for when the component is destroyed when the timer runs out;

--- CommunityEntity.UI.cs
+++ CommunityEntity.UI.cs
@@ -1,4 +1,4 @@
-using UnityEngine;
+using UnityEngine;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
@@ -116,29 +116,6 @@
                     break;
                 }

-            case "CountDown":
-                {
-                    var c = go.AddComponent<UnityEngine.UI.Text>();
-                    c.text = obj.GetString( "wrapperText", "%TIME_LEFT% sec." );
-                    c.fontSize = obj.GetInt( "fontSize", 14 );
-                    c.font = FileSystem.Load<Font>( "Assets/Content/UI/Fonts/" + obj.GetString( "font", "RobotoCondensed-Bold.ttf" ) );
-                    c.alignment = (TextAnchor)System.Enum.Parse( typeof( TextAnchor ), obj.GetString( "align", "UpperLeft" ) );
-                    c.color = ColorEx.Parse( obj.GetString( "color", "1.0 1.0 1.0 1.0" ) );
-
-                    var cd = go.AddComponent<CountDown>();
-                    cd.timeLeft = obj.GetInt( "timeLeft", 0 );
-                    cd.wrapperText = obj.GetString( "wrapperText", "%TIME_LEFT% sec." );
-
-                    if ( obj.ContainsKey( "command" ) )
-                    {
-                        cd.command = obj.GetString( "command" );
-                    }
-
-                    GraphicComponentCreated( c, obj );
-                    break;
-                }
-
-
             case "UnityEngine.UI.Image":
                 {
                     var c = go.AddComponent<UnityEngine.UI.Image>();
@@ -221,6 +198,7 @@
                     c.useGraphicAlpha = obj.ContainsKey( "useGraphicAlpha" );
                     break;
                 }
+
             case "NeedsCursor":
                 {
                     go.AddComponent<NeedsCursor>();
@@ -237,6 +215,19 @@
                         rt.offsetMin = Vector2Ex.Parse( obj.GetString( "offsetmin", "0.0 0.0" ) );
                         rt.offsetMax = Vector2Ex.Parse( obj.GetString( "offsetmax", "1.0 1.0" ) );
                     }
+                    break;
+                }
+
+            case "Countdown":
+                {
+                    var c = go.AddComponent<Countdown>();
+                    c.timeLeft = obj.GetInt( "timeLeft", 0 );
+
+                    if ( obj.ContainsKey( "command" ) )
+                    {
+                        c.command = obj.GetString( "command" );
+                    }
+
                     break;
                 }

@@ -404,36 +395,48 @@
         }
     }

-    private class CountDown : MonoBehaviour
+    private class Countdown : MonoBehaviour
     {
         public string command = "";
-
         public int timeLeft = 0;
-        string placeHolderText = "%TIME_LEFT%";
-        public string wrapperText = "";
-
-        UnityEngine.UI.Text text;
+        private UnityEngine.UI.Text textComponent;

         void Start()
         {
-            text = GetComponent<UnityEngine.UI.Text>();
-            text.text = wrapperText.Replace( placeHolderText, timeLeft.ToString() );
-            InvokeRepeating( "UpdateCountDown", 1f, 1f );
-        }
-
-        void UpdateCountDown()
+            textComponent = GetComponent<UnityEngine.UI.Text>();
+            if ( textComponent )
+            {
+                textComponent.text = textComponent.text.Replace( "%TIME_LEFT%", timeLeft.ToString() );
+            }
+            InvokeRepeating( "UpdateCountdown", 1f, 1f );
+        }
+
+        void UpdateCountdown()
         {
             timeLeft--;

             if ( timeLeft == -1 )
             {
                 if ( !string.IsNullOrEmpty( command ) )
+                {
                     ConsoleNetwork.ClientRunOnServer( command );
-
-                Destroy( gameObject );
-            }
-
-            text.text = wrapperText.Replace( placeHolderText, timeLeft.ToString() );
+                }
+
+                var fadeOut = GetComponent<FadeOut>();
+                if ( fadeOut )
+                {
+                    fadeOut.FadeOutAndDestroy();
+                }
+                else
+                {
+                    Object.Destroy( gameObject );
+                }
+            }
+
+            if ( textComponent )
+            {
+                textComponent.text = textComponent.text.Replace( "%TIME_LEFT%", timeLeft.ToString() );
+            }
         }
     }


Update DeviceClasses.cs

Update DeviceClasses.cs

--- RegistryPlugin.DeviceClasses/DeviceClasses.cs
+++ RegistryPlugin.DeviceClasses/DeviceClasses.cs
@@ -65,9 +65,6 @@
             string Name = keyName[1];
             string serialNumber = keyName[2];

-            if (serialNumber.StartsWith("{"))
-                serialNumber = null;
-
             string regexData = null;

             DateTimeOffset? ts = subKey.LastWriteTime;


New method of Pattern Matching

New method of Pattern Matching

--- SampleCSharp7/FirstConsoleApp/PersonHelper.cs
+++ SampleCSharp7/FirstConsoleApp/PersonHelper.cs
@@ -37,7 +37,28 @@
         /// Pattern Matching with "is" keyword
         /// </summary>
         /// <param name="person"></param>
-        public void PrintDataWithPatternMatching(object person)
+        public void PrintDataWithIsKeyword(object person)
+        {
+            if (person is null)
+            {
+                WriteLine($" Object {nameof(person)} is null");
+            }
+
+            if (person is Student studentObject)
+            {
+                WriteLine($"\nStudent {studentObject.Name} {studentObject.LastName} is enrolled for courses {string.Join(", ", studentObject.CourseCodes)}");
+            }
+            if (person is Professor professorObject)
+            {
+                WriteLine($"\nProfessor {professorObject.Name} {professorObject.LastName} teaches {string.Join<string>(", ", professorObject.TeachesSubject)}");
+            }
+        }
+
+        /// <summary>
+        /// Pattern Matching with Swtich case
+        /// </summary>
+        /// <param name="person"></param>
+        public void PrintDataWithSwitchCase(object person)
         {
             if (person is null)
             {


use Html.Raw with column value

use Html.Raw with column value

--- Samples/Company.Mvc/Views/Shared/Index.cshtml
+++ Samples/Company.Mvc/Views/Shared/Index.cshtml
@@ -86,7 +86,7 @@

-
Detail

+
Detail

                                             @if (Model.Metadata.EditRoles.All(t => User.IsInRole(t)))
                                             {

Edit


Added fix for possible mono bug in DPSManager constructor. Fixed by using try catch and falling back to the older method.

--- DPSBase/DPSManager.cs
+++ DPSBase/DPSManager.cs
@@ -345,27 +345,39 @@
             //of serializers and compressors. On windows phone we cannot get a list of referenced assemblies so we can only do this for already loaded assemblies.  Any others that are used will
             //have to be added manually.  On windows this will be done from a new app domain so we can unload it afterwards

+            AssemblyLoader loader;
+            ProcessArgument args;
 #if !WINDOWS_PHONE
-
-            AppDomain tempDomain = AppDomain.CreateDomain("Temp");
-            var loader = (AssemblyLoader)tempDomain.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
-
-            var args = new ProcessArgument();
-            args.entryDomain = Assembly.GetEntryAssembly().FullName;
-
-            loader.ProcessApplicationAssemblies(args);
-
-            //app.DoCallBack(new CrossAppDomainDelegate(loader.ProcessApplicationAssemblies));
-            AppDomain.Unload(tempDomain);
-            tempDomain = null;
-            GC.Collect();
-
+            try
+            {
+                AppDomain tempDomain = AppDomain.CreateDomain("Temp");
+
+                loader = (AssemblyLoader)tempDomain.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
+
+                args = new ProcessArgument();
+                args.entryDomain = Assembly.GetEntryAssembly().FullName;
+
+                loader.ProcessApplicationAssemblies(args);
+
+                //app.DoCallBack(new CrossAppDomainDelegate(loader.ProcessApplicationAssemblies));
+                AppDomain.Unload(tempDomain);
+                tempDomain = null;
+                GC.Collect();
+            }
+            catch (FileNotFoundException)
+            {
+                //In mono the above load method may not work so we will fall back to our older way of doing the same
+                //The disadvantage of this approach is that all assemblies are loaded and then stay in memory increasing the footprint slightly
+                loader = new AssemblyLoader();
+                args = new ProcessArgument();
+
+                loader.ProcessApplicationAssemblies(args);
+            }
 #else
             var loader = new AssemblyLoader();
             var args = new ProcessArgument();

             loader.ProcessApplicationAssemblies(args);
-
 #endif

             foreach (var serializer in args.serializerTypes)


Added comment about CORS

Added comment about CORS

--- SettingsService.Api/App_Start/WebApiConfig.cs
+++ SettingsService.Api/App_Start/WebApiConfig.cs
@@ -9,6 +9,7 @@
         {
             // good explanation of CORS https://www.html5rocks.com/en/tutorials/cors/
             // enable CORS. read more https://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
+            // detailed clarification https://msdn.microsoft.com/en-us/magazine/dn532203.aspx
             config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

             // Web API routes


Fiddle the series order to ensure the moving average is on top

This isn’t as the documentation would suggest, but it works.

--- ShinobiStockChart/StockChartViewController.cs
+++ ShinobiStockChart/StockChartViewController.cs
@@ -166,7 +166,8 @@

             protected override SChartData[] GetDataPoints (ShinobiChart chart, int seriesIndex)
             {
-                if (seriesIndex == 0) {
+                // Bit convoluted to get the z-index correct
+                if(_movingAverageDataPoints == null || seriesIndex == 1) {
                     return _dataPoints.ToArray ();
                 } else {
                     return _movingAverageDataPoints.ToArray ();
@@ -184,7 +185,8 @@

             public override int GetNumberOfDataPoints (ShinobiChart chart, int seriesIndex)
             {
-                if(seriesIndex == 0) {
+                // Bit convoluted to get the z-index correct
+                if(_movingAverageDataPoints == null || seriesIndex == 1) {
                     return _dataPoints.Count;
                 } else {
                     return _movingAverageDataPoints.Count;
@@ -195,7 +197,8 @@
             {
                 var lineSeries = new SChartLineSeries ();

-                if (index == 0) {
+                // Bit convoluted to get the z-index correct
+                if (_movingAverageDataPoints == null || index == 1) {
                     lineSeries.Style.AreaLineColor = TintColor;
                     lineSeries.Style.AreaColor = TintColor.ColorWithAlpha (0.1f);
                     lineSeries.Style.AreaColorLowGradient = TintColor.ColorWithAlpha (0.8f);
@@ -203,8 +206,8 @@
                     lineSeries.Style.ShowFill = true;
                     lineSeries.CrosshairEnabled = true;
                 } else {
-                    lineSeries.Style.LineColor = UIColor.Red;
-                    lineSeries.Style.LineWidth = 2.0;
+                    lineSeries.Style.LineColor = UIColor.Red.ColorWithAlpha (0.8f);
+                    lineSeries.Style.LineWidth = 1.0;
                     lineSeries.Style.ShowFill = false;
                     lineSeries.CrosshairEnabled = false;
                 }


(TFS CS 4494)

(TFS CS 4494)

--- Signum.Engine.Extensions/Basics/QueryLogic.cs
+++ Signum.Engine.Extensions/Basics/QueryLogic.cs
@@ -42,7 +42,12 @@

         static void Schema_Initializing(Schema sender)
         {
-            QueryNames = DynamicQueryManager.Current.GetQueryNames().ToDictionary(qn => QueryUtils.GetQueryName(qn));
+            QueryNames = CreateQueryNames();
+        }
+
+        private static Dictionary<string, object> CreateQueryNames()
+        {
+            return DynamicQueryManager.Current.GetQueryNames().ToDictionary(qn => QueryUtils.GetQueryName(qn));
         }

         public static List<QueryDN> RetrieveOrGenerateQueries()
@@ -58,7 +63,7 @@

         static SqlPreCommand SynchronizeQueries(Replacements replacements)
         {
-            var should = QueryNames.Select(kvp => new QueryDN { Name = kvp.Key });
+            var should = CreateQueryNames().Select(kvp => new QueryDN { Name = kvp.Key });

             var current = Administrator.TryRetrieveAll<QueryDN>(replacements);


remove waiting because drop area could be hidden

--- Signum.Web.Extensions.Selenium/LineProxies.cs
+++ Signum.Web.Extensions.Selenium/LineProxies.cs
@@ -801,7 +801,6 @@

         public void SetPath(string path)
         {
-            Selenium.WaitElementVisible(By.CssSelector("#{0}_DivNew .sf-file-drop".FormatWith(Prefix)));
             Selenium.FindElement(By.CssSelector("#{0}_sfFile".FormatWith(Prefix))).SendKeys(path);
             //Selenium.FireEvent("{0}_sfFile".FormatWith(Prefix), "change");
             Selenium.Wait(() =>


Fixed a problem where a file could be left open if the backup was stopped.

Update issue #296

I found and fixed a situation where a single file could be left open if the backup was stopped/aborted.

git-svn-id: bf0bd98e94caaaf89d4dca54eab1bef459ce3438@1173 59da171f-624f-0410-aa54-27559c288bec

--- Duplicati/Library/Main/RSyncDir.cs
+++ Duplicati/Library/Main/RSyncDir.cs
@@ -2224,6 +2224,13 @@

         public void Dispose()
         {
+            if (m_lastPartialFile != null)
+            {
+                try { m_lastPartialFile.Dispose(); }
+                catch { }
+                m_lastPartialFile = null;
+            }
+
             if (m_patches != null)
             {
                 foreach (Library.Interface.ICompression arc in m_patches)


GSF.Core: Made improvements to file enumeration in FileProcessor.

--- Source/Libraries/GSF.Core/IO/FileProcessor.cs
+++ Source/Libraries/GSF.Core/IO/FileProcessor.cs
@@ -23,13 +23,11 @@

 using System;
 using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
-using System.Text;
 using System.Threading;
+using System.Threading.Tasks;
 using System.Timers;
-using GSF.Collections;
 using GSF.Threading;
 using CancellationToken = GSF.Threading.CancellationToken;
 using Timer = System.Timers.Timer;
@@ -160,15 +158,17 @@
         #region [ Members ]

         // Nested Types
-
         private sealed class TrackedDirectory : IDisposable
         {
             #region [ Members ]

+            // Events
+            private event EventHandler<EventArgs<List<string>>> ActivelyVisitedPathsRequested;
+
             // Fields
-            private FileProcessor m_fileProcessor;
+            private readonly FileProcessor m_fileProcessor;
             private SafeFileWatcher m_fileWatcher;
-            private FileEnumerator m_fileEnumerator;
+            private CancellationToken m_cancellationToken;
             private bool m_disposed;

             #endregion
@@ -188,19 +188,14 @@

             public string Path { get; }

-            public bool IsEnumerating
+            public List<string> ActivelyEnumeratedPaths
             {
                 get
                 {
-                    return m_fileEnumerator?.Active ?? false;
-                }
-            }
-
-            public List<string> ActivelyEnumeratedPaths
-            {
-                get
-                {
-                    return m_fileEnumerator.ActivelyVisitedPaths;
+                    List<string> activelyVisitedPaths = new List<string>();
+                    EventArgs<List<string>> args = new EventArgs<List<string>>(activelyVisitedPaths);
+                    ActivelyVisitedPathsRequested?.Invoke(this, args);
+                    return activelyVisitedPaths;
                 }
             }

@@ -208,22 +203,21 @@

             #region [ Methods ]

-            public void EnumerateFiles(FileEnumerationStrategy fileEnumerationStrategy)
+            public async Task EnumerateAsync(FileEnumerationStrategy fileEnumerationStrategy)
             {
                 if (m_disposed)
                     return;

-                if (IsEnumerating)
-                    throw new InvalidOperationException("Enumeration is already in progress.");
-
                 CancellationToken cancellationToken = new CancellationToken();
-                m_fileEnumerator = new FileEnumerator(Path, m_fileProcessor, cancellationToken);
-                m_fileEnumerator.Enumerate(fileEnumerationStrategy);
+                Interlocked.Exchange(ref m_cancellationToken, cancellationToken);
+                DirectoryInfo directory = new DirectoryInfo(Path);
+                await EnumerateDirectoryAsync(directory, fileEnumerationStrategy, cancellationToken);
             }

             public void CancelEnumeration()
             {
-                m_fileEnumerator?.Cancel();
+                CancellationToken cancellationToken = Interlocked.CompareExchange(ref m_cancellationToken, null, null);
+                cancellationToken?.Cancel();
             }

             public void CheckFileWatcher()
@@ -238,9 +232,6 @@
                 }
             }

-            /// <summary>
-            /// Releases all the resources used by the <see cref="TrackedDirectory"/> object.
-            /// </summary>
             public void Dispose()
             {
                 if (m_disposed)
@@ -254,6 +245,128 @@
                 finally
                 {
                     m_disposed = true;
+                }
+            }
+
+            private async Task EnumerateDirectoryAsync(DirectoryInfo directory, FileEnumerationStrategy fileEnumerationStrategy, CancellationToken cancellationToken)
+            {
+                bool parallelSubdirectories =
+                    fileEnumerationStrategy == FileEnumerationStrategy.ParallelSubdirectories;
+
+                LogicalThread thread = LogicalThread.CurrentThread;
+
+                if (parallelSubdirectories)
+                {
+                    LogicalThreadScheduler scheduler = m_fileProcessor.m_threadScheduler;
+                    thread = scheduler.CreateThread();
+                    await thread.Join();
+                }
+
+                async Task ForEach(IEnumerable<Task> tasks)
+                {
+                    foreach (Task task in tasks)
+                    {
+                        if (cancellationToken.IsCancelled)
+                            return;
+
+                        await task;
+                        await thread.Yield();
+                    }
+                }
+
+                LogicalThread processingThread = m_fileProcessor.m_processingThread;
+                string activePath = null;
+
+                async Task VisitSubdirectoryAsync(DirectoryInfo subdirectory)
+                {
+                    activePath = subdirectory.FullName;
+
+                    if (cancellationToken.IsCancelled)
+                        return;
+
+                    if (m_fileProcessor.MatchesFolderExclusion(subdirectory.FullName))
+                        return;
+
+                    await EnumerateDirectoryAsync(subdirectory, fileEnumerationStrategy, cancellationToken);
+                }
+
+                async Task<Task> VisitFileAsync(FileInfo file)
+                {
+                    activePath = file.FullName;
+
+                    if (cancellationToken.IsCancelled)
+                        return Task.CompletedTask;
+
+                    async Task InvokeOnProcessingThread(Action action)
+                    {
+                        await processingThread.Join();
+                        action();
+                    }
+
+                    DateTime lastWriteTime = file.LastWriteTimeUtc;
+                    void Process() => m_fileProcessor.TouchAndProcess(file.FullName, lastWriteTime, false);
+                    void Skip() => m_fileProcessor.TouchAndSkip(file.FullName, lastWriteTime);
+
+                    if (!m_fileProcessor.MatchesFilter(file.FullName))
+                        return InvokeOnProcessingThread(Skip);
+
+                    Task processTask = InvokeOnProcessingThread(Process);
+                    await thread.Yield();
+                    return processTask;
+                }
+
+                async Task EnumerateSubdirectoriesAsync()
+                {
+                    Func<IEnumerable<Task>, Task> whenAll =
+                        parallelSubdirectories ? Task.WhenAll : ForEach;
+
+                    IEnumerable<Task> subdirectoryTasks = FilePath
+                        .EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly, m_fileProcessor.OnError)
+                        .Select(VisitSubdirectoryAsync);
+
+                    await whenAll(subdirectoryTasks);
+                }
+
+                async Task EnumerateFilesAsync()
+                {
+                    IEnumerable<Task<Task>> fileTasks = FilePath
+                        .EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly)
+                        .Select(VisitFileAsync);
+
+                    List<Task> processTasks = new List<Task>();
+
+                    foreach (Task<Task> fileTask in fileTasks)
+                        processTasks.Add(await fileTask);
+
+                    await Task.WhenAll(processTasks);
+                }
+
+                EventHandler<EventArgs<List<string>>> handler = (sender, args) =>
+                {
+                    if (!(activePath is null))
+                        args.Argument.Add(activePath);
+                };
+
+                try
+                {
+                    ActivelyVisitedPathsRequested += handler;
+
+                    if (parallelSubdirectories)
+                    {
+                        Task subdirectoriesTask = EnumerateSubdirectoriesAsync();
+                        await EnumerateFilesAsync();
+                        ActivelyVisitedPathsRequested -= handler;
+                        await subdirectoriesTask;
+                    }
+                    else
+                    {
+                        await EnumerateFilesAsync();
+                        await EnumerateSubdirectoriesAsync();
+                    }
+                }
+                finally
+                {
+                    ActivelyVisitedPathsRequested -= handler;
                 }
             }

@@ -280,411 +393,6 @@
                 m_fileWatcher.Deleted -= m_fileProcessor.Watcher_Deleted;
                 m_fileWatcher.Error -= m_fileProcessor.Watcher_Error;
                 m_fileWatcher.Dispose();
-            }
-
-            #endregion
-        }
-
-        private class FileEnumerator
-        {
-            #region [ Members ]
-
-            // Fields
-            private readonly FileProcessor m_fileProcessor;
-            private readonly CancellationToken m_cancellationToken;
-            private string m_lastVisitedPath;
-            private bool m_active;
-
-            #endregion
-
-            #region [ Constructors ]
-
-            public FileEnumerator(string path, FileProcessor fileProcessor, CancellationToken cancellationToken)
-            {
-                Path = path;
-                m_fileProcessor = fileProcessor;
-                m_cancellationToken = cancellationToken;
-            }
-
-            #endregion
-
-            #region [ Properties ]
-
-            public string Path { get; }
-            public FileEnumerator[] SubdirectoryEnumerators { get; private set; } = new FileEnumerator[0];
-
-            public bool Active
-            {
-                get
-                {
-                    if (m_active)
-                        return true;
-
-                    FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators;
-                    return m_active || subdirectoryEnumerators.Any(enumerator => enumerator.Active);
-                }
-            }
-
-            public List<string> ActivelyVisitedPaths
-            {
-                get
-                {
-                    List<string> activelyVisitedPaths = new List<string>();
-                    BuildActivelyVisitedPaths(activelyVisitedPaths);
-                    return activelyVisitedPaths;
-                }
-            }
-
-            #endregion
-
-            #region [ Methods ]
-
-            public void Enumerate(FileEnumerationStrategy enumerationStrategy)
-            {
-                if (Active)
-                    throw new InvalidOperationException("Enumeration is already in progress.");
-
-                if ((object)LogicalThread.CurrentThread == null)
-                    throw new InvalidOperationException("Enumeration must be executed on a logical thread.");
-
-                m_active = true;
-
-                if (enumerationStrategy != FileEnumerationStrategy.ParallelSubdirectories)
-                {
-                    SubdirectoryEnumerators = new FileEnumerator[0];
-
-                    // If subdirectories do not need to be processed in parallel,
-                    // we can simply process a recursive call to enumerate all files
-                    IEnumerable<string> fileEnumerable = EnumerateFilesRecursively(Path);
-                    EnumerateNextFile(new EnumerableWrapper(fileEnumerable, m_cancellationToken));
-                }
-                else
-                {
-                    // To process subdirectories in parallel, we need to create a recursive structure
-                    // of enumerators to process each subdirectory on its own logical thread
-                    InitializeSubdirectoryEnumerators();
-                    EnumerateNextDirectory(0);
-                }
-            }
-
-            public void Cancel()
-            {
-                m_cancellationToken.Cancel();
-            }
-
-            // Recursively enumerates files under the given path, ignoring subfolders based on folder exclusion setting.
-            private IEnumerable<string> EnumerateFilesRecursively(string path)
-            {
-                try
-                {
-                    IEnumerable<string> subDirectoryEnumerable = FilePath.EnumerateDirectories(path, exceptionHandler: m_fileProcessor.OnError)
-                        .Where(directory => !m_fileProcessor.MatchesFolderExclusion(directory))
-                        .SelectMany(EnumerateFilesRecursively);
-
-                    return FilePath.EnumerateFiles(path, exceptionHandler: m_fileProcessor.OnError).Concat(subDirectoryEnumerable);
-                }
-                catch (Exception ex)
-                {
-                    m_fileProcessor.OnError(ex);
-                    return null;
-                }
-            }
-
-            // Initializes the FileEnumerators for subdirectories.
-            private void InitializeSubdirectoryEnumerators()
-            {
-                // Immediately enumerate all subdirectories so we can build the array of FileEnumerators.
-                // Using a fixed-size collection like an array greatly simplifies multithreaded access to the collection.
-                using (EnumerableWrapper wrapper = new EnumerableWrapper(EnumerateDirectories(), m_cancellationToken))
-                {
-                    List<string> subDirectories = new List<string>();
-
-                    while (wrapper.MoveNext())
-                    {
-                        if (!m_fileProcessor.MatchesFolderExclusion(wrapper.Current))
-                            subDirectories.Add(wrapper.Current);
-                    }
-
-                    SubdirectoryEnumerators = subDirectories
-                        .Select(path => new FileEnumerator(path, m_fileProcessor, m_cancellationToken))
-                        .ToArray();
-                }
-            }
-
-            // Enumerates the next subdirectory in the list of subdirectory enumerators.
-            private void EnumerateNextDirectory(int index)
-            {
-                try
-                {
-                    m_active = !m_cancellationToken.IsCancelled;
-
-                    if (!m_active)
-                        return;
-
-                    FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators;
-
-                    if (index >= subdirectoryEnumerators.Length)
-                    {
-                        EnumerateNextFile(new EnumerableWrapper(EnumerateFiles(), m_cancellationToken));
-                        return;
-                    }
-
-                    // Create a new thread for the subdirectory since this code is only invoked if the file
-                    // processor is using the enumeration strategy for processing subdirectories in parallel
-                    LogicalThread subdirectoryThread = m_fileProcessor.m_threadScheduler.CreateThread();
-                    subdirectoryThread.Push(() => subdirectoryEnumerators[index].Enumerate(FileEnumerationStrategy.ParallelSubdirectories));
-
-                    // Invoke the asynchronous call to enumerate the next directory
-                    LogicalThread.CurrentThread.Push(() => EnumerateNextDirectory(index + 1));
-
-                    m_lastVisitedPath = subdirectoryEnumerators[index].Path;
-                }
-                catch
-                {
-                    m_active = false;
-                    throw;
-                }
-            }
-
-            // Advances enumeration until the next file that matches the filter.
-            private bool AdvanceToNextSubdirectory(EnumerableWrapper wrapper)
-            {
-                //List<string> skippedFiles = new List<string>();
-
-                while (true)
-                {
-                    if (!wrapper.MoveNext())
-                        return false;
-
-                    if (!m_fileProcessor.MatchesFolderExclusion(wrapper.Current))
-                        return true;
-
-                    m_lastVisitedPath = wrapper.Current;
-                }
-            }
-
-            // Enumerates the next file in the given enumerable.
-            private void EnumerateNextFile(EnumerableWrapper wrapper)
-            {
-                try
-                {
-                    m_active = AdvanceToNextMatch(wrapper);
-
-                    if (!m_active)
-                        return;
-
-                    ProcessCurrentFileAndEnumerateNextFile(wrapper);
-                    m_lastVisitedPath = wrapper.Current;
-                }
-                catch
-                {
-                    m_active = false;
-                    wrapper.Dispose();
-                    throw;
-                }
-                finally
-                {
-                    if (!wrapper.LastMove)
-                    {
-                        m_active = false;
-                        wrapper.Dispose();
-                    }
-                }
-            }
-
-            // Advances enumeration until the next file that matches the filter.
-            private bool AdvanceToNextMatch(EnumerableWrapper wrapper)
-            {
-                List<string> skippedFiles = new List<string>();
-
-                try
-                {
-                    while (true)
-                    {
-                        if (!wrapper.MoveNext())
-                            return false;
-
-                        if (m_fileProcessor.MatchesFilter(wrapper.Current))
-                            return true;
-
-                        skippedFiles.Add(wrapper.Current);
-                        m_lastVisitedPath = wrapper.Current;
-                    }
-                }
-                finally
-                {
-                    m_fileProcessor.m_processingThread.Push(1, () =>
-                    {
-                        foreach (string filePath in skippedFiles)
-                            m_fileProcessor.TouchAndSkip(filePath);
-                    });
-                }
-            }
-
-            // Processes the current file in the given enumerable.
-            private void ProcessCurrentFileAndEnumerateNextFile(EnumerableWrapper wrapper)
-            {
-                LogicalThread enumerationThread = LogicalThread.CurrentThread;
-                string filePath = wrapper.Current;
-
-                // Kick off the operation to process the current file,
-                // but don't enumerate to the next file until processing
-                // for the current file has begun
-                m_fileProcessor.m_processingThread.Push(1, () =>
-                {
-                    // Check the state of cancellation for the
-                    // enumeration thread on the processing
-                    // thread as well to speed up cancellation
-                    if (m_cancellationToken.IsCancelled)
-                    {
-                        m_active = false;
-                        wrapper.Dispose();
-                        return;
-                    }
-
-                    enumerationThread.Push(() => EnumerateNextFile(wrapper));
-                    m_fileProcessor.TouchAndProcess(filePath, false);
-                });
-            }
-
-            private IEnumerable<string> EnumerateDirectories()
-            {
-                IEnumerable<string> directoryEnumerable = Directory.EnumerateDirectories(Path);
-
-                return m_fileProcessor.OrderedEnumeration
-                    ? directoryEnumerable.OrderBy(dir => dir)
-                    : directoryEnumerable;
-            }
-
-            private IEnumerable<string> EnumerateFiles()
-            {
-                IEnumerable<string> fileEnumerable = Directory.EnumerateFiles(Path);
-
-                return m_fileProcessor.OrderedEnumeration
-                    ? fileEnumerable.OrderBy(dir => dir)
-                    : fileEnumerable;
-            }
-
-            private void BuildActivelyVisitedPaths(List<string> activelyVisitedPaths)
-            {
-                string lastVisitedPath = m_lastVisitedPath;
-
-                if (m_active && (object)lastVisitedPath != null)
-                    activelyVisitedPaths.Add(lastVisitedPath);
-
-                FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators;
-
-                foreach (FileEnumerator enumerator in subdirectoryEnumerators)
-                    enumerator.BuildActivelyVisitedPaths(activelyVisitedPaths);
-            }
-
-            #endregion
-        }
-
-        private class EnumerableWrapper : IDisposable
-        {
-            #region [ Members ]
-
-            // Fields
-            private IEnumerable<string> m_enumerable;
-            private IEnumerator<string> m_enumerator;
-            private bool m_lastMove;
-            private string m_current;
-
-            private CancellationToken m_cancellationToken;
-            private bool m_disposed;
-
-            #endregion
-
-            #region [ Constructors ]
-
-            /// <summary>
-            /// Creates a new instance of the <see cref="EnumerableWrapper"/> class.
-            /// </summary>
-            /// <param name="enumerable">The enumerable to be wrapped.</param>
-            /// <param name="cancellationToken">The token used to cancel the enumeration operation.</param>
-            public EnumerableWrapper(IEnumerable<string> enumerable, CancellationToken cancellationToken)
-            {
-                if ((object)enumerable == null)
-                    throw new ArgumentNullException(nameof(enumerable));
-
-                m_enumerable = enumerable;
-                m_enumerator = enumerable.GetEnumerator();
-                m_cancellationToken = cancellationToken;
-            }
-
-            #endregion
-
-            #region [ Properties ]
-
-            /// <summary>
-            /// Gets the value returned by the
-            /// last call to <see cref="MoveNext"/>.
-            /// </summary>
-            public bool LastMove
-            {
-                get
-                {
-                    return m_lastMove;
-                }
-            }
-
-            /// <summary>
-            /// Gets the current value of the enumerator.
-            /// </summary>
-            public string Current
-            {
-                get
-                {
-                    return m_current;
-                }
-            }
-
-            /// <summary>
-            /// Gets the cancellation token for this enumerator.
-            /// </summary>
-            public CancellationToken CancellationToken
-            {
-                get
-                {
-                    return m_cancellationToken;
-                }
-            }
-
-            #endregion
-
-            #region [ Methods ]
-
-            /// <summary>
-            /// Advances the enumerator to the next element of the collection.
-            /// </summary>
-            /// <returns>True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
-            public bool MoveNext()
-            {
-                m_lastMove = (!m_cancellationToken.IsCancelled) && m_enumerator.MoveNext();
-                m_current = m_lastMove ? m_enumerator.Current : null;
-                return m_lastMove;
-            }
-
-            /// <summary>
-            /// Releases all the resources used by the enumerable.
-            /// </summary>
-            public void Dispose()
-            {
-                if (!m_disposed)
-                {
-                    try
-                    {
-                        (m_enumerator as IDisposable)?.Dispose();
-                        (m_enumerable as IDisposable)?.Dispose();
-                    }
-                    finally
-                    {
-                        m_lastMove = false;
-                        m_current = null;
-                        m_disposed = true;
-                    }
-                }
             }

             #endregion
@@ -746,13 +454,14 @@

         private readonly object m_trackedDirectoriesLock;
         private readonly List<TrackedDirectory> m_trackedDirectories;
-        private LogicalThread m_sequentialEnumerationThread;
-
-        private LogicalThreadScheduler m_threadScheduler;
-        private LogicalThread m_processingThread;
-        private LogicalThread m_watcherThread;
-        private Timer m_fileWatchTimer;
-        private ManualResetEvent m_waitObject;
+        private readonly TaskSynchronizedOperation m_enumerationOperation;
+
+        private readonly LogicalThreadScheduler m_threadScheduler;
+        private readonly LogicalThread m_processingThread;
+        private readonly LogicalThread m_watcherThread;
+        private readonly LogicalThread m_sequentialEnumerationThread;
+        private readonly Timer m_fileWatchTimer;
+        private readonly CancellationTokenSource m_requeueTokenSource;

         private readonly Dictionary<string, DateTime> m_touchedFiles;

@@ -780,6 +489,8 @@

             m_trackedDirectoriesLock = new object();
             m_trackedDirectories = new List<TrackedDirectory>();
+            m_enumerationOperation = new TaskSynchronizedOperation(EnumerateWatchDirectoriesAsync, OnError);
+
             m_threadScheduler = new LogicalThreadScheduler(2);
             m_threadScheduler.UnhandledException += (sender, args) => OnError(args.Argument);
             m_processingThread = m_threadScheduler.CreateThread();
@@ -787,7 +498,7 @@
             m_sequentialEnumerationThread = m_threadScheduler.CreateThread();
             m_fileWatchTimer = new Timer(15000);
             m_fileWatchTimer.Elapsed += FileWatchTimer_Elapsed;
-            m_waitObject = new ManualResetEvent(false);
+            m_requeueTokenSource = new CancellationTokenSource();

             m_touchedFiles = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
         }
@@ -859,7 +570,7 @@

                     if (m_cachePath != value)
                     {
-                        if ((object)m_cachePath != null)
+                        if (!(m_cachePath is null))
                             m_cachePath = FilePath.GetAbsolutePath(value);
                         else
                             m_cachePath = DefaultCachePath;
@@ -967,16 +678,7 @@
         /// <summary>
         /// Gets the flag indicating if the file processor is actively enumerating.
         /// </summary>
-        public bool IsEnumerating
-        {
-            get
-            {
-                lock (m_trackedDirectoriesLock)
-                {
-                    return m_trackedDirectories.Any(dir => dir.IsEnumerating);
-                }
-            }
-        }
+        public bool IsEnumerating => m_enumerationOperation.IsRunning;

         /// <summary>
         /// Gets the number of files
@@ -1039,14 +741,6 @@
                 trackedDirectory = new TrackedDirectory(this, fullPath);
                 m_trackedDirectories.Add(trackedDirectory);
             }
-
-            FileEnumerationStrategy enumerationStrategy = m_enumerationStrategy;
-
-            LogicalThread enumerationThread = (enumerationStrategy == FileEnumerationStrategy.Sequential)
-                    ? m_sequentialEnumerationThread
-                    : m_threadScheduler.CreateThread();
-
-            enumerationThread.Push(() => trackedDirectory.EnumerateFiles(enumerationStrategy));
         }

         /// <summary>
@@ -1085,27 +779,7 @@
             if (m_disposed)
                 throw new ObjectDisposedException(nameof(FileProcessor));

-            List<TrackedDirectory> trackedDirectories;
-
-            lock (m_trackedDirectoriesLock)
-            {
-                trackedDirectories = new List<TrackedDirectory>(m_trackedDirectories);
-            }
-
-            FileEnumerationStrategy enumerationStrategy = m_enumerationStrategy;
-
-            LogicalThread GetThread()
-            {
-                return (enumerationStrategy == FileEnumerationStrategy.Sequential)
-                    ? m_sequentialEnumerationThread
-                    : m_threadScheduler.CreateThread();
-            }
-
-            foreach (TrackedDirectory trackedDirectory in trackedDirectories)
-            {
-                if (!trackedDirectory.IsEnumerating)
-                    GetThread().Push(() => trackedDirectory.EnumerateFiles(enumerationStrategy));
-            }
+            m_enumerationOperation.RunOnceAsync();
         }

         /// <summary>
@@ -1185,6 +859,9 @@
         /// </remarks>
         public void ResetIndexAndStatistics()
         {
+            if (m_disposed)
+                throw new ObjectDisposedException(nameof(FileProcessor));
+
             m_processingThread.Push(2, () =>
             {
                 m_touchedFiles.Clear();
@@ -1197,7 +874,6 @@
         /// <summary>
         /// Releases all the resources used by the <see cref="FileProcessor"/> object.
         /// </summary>
-        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_waitObject", Justification = "Thread pool threads could still be using this object.")]
         public void Dispose()
         {
             if (!m_disposed)
@@ -1207,68 +883,114 @@
                     StopEnumeration();
                     ClearTrackedDirectories();

-                    if ((object)m_fileWatchTimer != null)
+                    if (!(m_fileWatchTimer is null))
+                        m_fileWatchTimer.Dispose();
+
+                    if (!(m_requeueTokenSource is null))
                     {
-                        m_fileWatchTimer.Dispose();
-                        m_fileWatchTimer = null;
+                        Interlocked.Increment(ref m_requeuedFileCount);
+                        m_requeueTokenSource.Cancel();
+
+                        int requeuedFileCount = Interlocked.Decrement(ref m_requeuedFileCount);
+
+                        if (requeuedFileCount == 0)
+                            m_requeueTokenSource.Dispose();
                     }
-
-                    if ((object)m_waitObject != null)
-                    {
-                        // DO NOT dispose of the wait object
-                        // We have to rely on the garbage collector to reclaim this
-                        // one or else we risk unhandled exceptions on the thread pool
-                        m_waitObject.Set();
-                        m_waitObject = null;
-                    }
                 }
                 finally
                 {
                     m_disposed = true; // Prevent duplicate dispose.
                 }
             }
+        }
+
+        // Forces enumeration of directories currently being watched.
+        private async Task EnumerateWatchDirectoriesAsync()
+        {
+            List<TrackedDirectory> GetTrackedDirectories()
+            {
+                lock (m_trackedDirectoriesLock)
+                    return new List<TrackedDirectory>(m_trackedDirectories);
+            }
+
+            List<TrackedDirectory> trackedDirectories = GetTrackedDirectories();
+            FileEnumerationStrategy enumerationStrategy = m_enumerationStrategy;
+            bool sequential = enumerationStrategy == FileEnumerationStrategy.Sequential;
+
+            Func<LogicalThread> getEnumerationThread = sequential
+                ? () => m_sequentialEnumerationThread
+                : () => m_threadScheduler.CreateThread();
+
+            async Task EnumerateTrackedDirectoryAsync(TrackedDirectory trackedDirectory)
+            {
+                try
+                {
+                    LogicalThread enumerationThread = getEnumerationThread();
+                    await enumerationThread.Join();
+                    await trackedDirectory.EnumerateAsync(enumerationStrategy);
+                }
+                catch (Exception ex)
+                {
+                    OnError(ex);
+                }
+            }
+
+            IEnumerable<Task> enumerationTasks = trackedDirectories
+                .Select(EnumerateTrackedDirectoryAsync);
+
+            async Task ForEach(IEnumerable<Task> tasks)
+            {
+                foreach (Task task in tasks)
+                    await task;
+            }
+
+            Func<IEnumerable<Task>, Task> whenAll =
+                sequential ? ForEach : Task.WhenAll;
+
+            await whenAll(enumerationTasks);
         }

         // Checks and updates the touchedFiles lookup table, then starts the processing loop.
-        private void TouchAndProcess(string filePath, bool raisedByFileWatcher)
-        {
-            DateTime lastWriteTime = File.GetLastWriteTimeUtc(filePath);
-            DateTime lastKnownWriteTime;
-
-            if (!m_touchedFiles.TryGetValue(filePath, out lastKnownWriteTime) || lastKnownWriteTime < lastWriteTime)
+        private void TouchAndProcess(string filePath, DateTime lastWriteTime, bool raisedByFileWatcher)
+        {
+            bool touched = m_touchedFiles.TryGetValue(filePath, out DateTime lastKnownWriteTime);
+
+            async Task ProcessAsync()
+            {
+                await RunProcessLoopAsync(filePath, raisedByFileWatcher);
+
+                if (!touched)
+                    Interlocked.Increment(ref m_processedFileCount);
+            }
+
+            if (!touched || lastKnownWriteTime < lastWriteTime)
             {
                 m_touchedFiles[filePath] = lastWriteTime;
-                StartProcessLoop(filePath, raisedByFileWatcher);
+                _ = ProcessAsync();
             }
         }

         // Checks and updates the touchedFiles lookup table, then increments the skipped file count.
-        private void TouchAndSkip(string filePath)
-        {
-            DateTime lastWriteTime = File.GetLastWriteTimeUtc(filePath);
-            DateTime lastKnownWriteTime;
-
-            if (!m_touchedFiles.TryGetValue(filePath, out lastKnownWriteTime) || lastKnownWriteTime < lastWriteTime)
+        private void TouchAndSkip(string filePath, DateTime lastWriteTime)
+        {
+            bool touched = m_touchedFiles.TryGetValue(filePath, out DateTime lastKnownWriteTime);
+
+            if (!touched || lastKnownWriteTime < lastWriteTime)
             {
                 m_touchedFiles[filePath] = lastWriteTime;
-                Interlocked.Increment(ref m_skippedFileCount);
-            }
-        }
-
-        // Starts a loop to process the file.
+
+                if (!touched)
+                    Interlocked.Increment(ref m_skippedFileCount);
+            }
+        }
+
+        // Runs an asynchronous loop to process the file.
         // Continues looping so long as the user continues requesting to requeue.
-        private void StartProcessLoop(string filePath, bool raisedByFileWatcher)
-        {
-            ManualResetEvent waitObject = m_waitObject;
-
-            if ((object)waitObject == null)
-                return;
-
+        private async Task RunProcessLoopAsync(string filePath, bool raisedByFileWatcher)
+        {
             int retryCount = 0;
-            Func<int> retryCounter = () => retryCount;
-            FileProcessorEventArgs args = new FileProcessorEventArgs(filePath, raisedByFileWatcher, retryCounter);
-
-            Action delayAndProcess = null;
+            int RetryCounter() => retryCount;
+            FileProcessorEventArgs args = new FileProcessorEventArgs(filePath, raisedByFileWatcher, RetryCounter);

             // 8 * 250 ms = 2 sec (cumulative: 2 sec)
             const int FastRetryLimit = 8;
@@ -1285,44 +1007,54 @@
             // After 60 seconds, continue with the slow retry delay
             const int SlowRetryDelay = 60000;

-            Action reprocessAction = () =>
-            {
+            int GetDelay()
+            {
+                if (retryCount < FastRetryLimit)
+                    return FastRetryDelay;
+
+                if (retryCount < QuickRetryLimit)
+                    return QuickRetryDelay;
+
+                if (retryCount < RelaxedRetryLimit)
+                    return RelaxedRetryDelay;
+
+                return SlowRetryDelay;
+            }
+
+            var cancellationToken = m_requeueTokenSource.Token;
+
+            while (true)
+            {
+                if (cancellationToken.IsCancellationRequested)
+                    break;
+
+                int priority = (retryCount < RelaxedRetryLimit) ? 2 : 1;
+                await m_processingThread.Join(priority);
+
+                if (cancellationToken.IsCancellationRequested)
+                    break;
+
                 ProcessFile(args);

-                if (args.Requeue)
-                {
-                    int delay;
-
-                    if (retryCount < FastRetryLimit)
-                        delay = FastRetryDelay;
-                    else if (retryCount < QuickRetryLimit)
-                        delay = QuickRetryDelay;
-                    else if (retryCount < RelaxedRetryLimit)
-                        delay = RelaxedRetryDelay;
-                    else
-                        delay = SlowRetryDelay;
-
-                    delayAndProcess.DelayAndExecute(waitObject, delay);
-
-                    return;
-                }
-
-                Interlocked.Decrement(ref m_requeuedFileCount);
-            };
-
-            delayAndProcess = () =>
-            {
-                int priority = (++retryCount < RelaxedRetryLimit) ? 2 : 1;
-                m_processingThread.Push(priority, reprocessAction);
-            };
-
-            ProcessFile(args);
-
-            if (!args.Requeue)
-                return;
-
-            Interlocked.Increment(ref m_requeuedFileCount);
-            delayAndProcess.DelayAndExecute(waitObject, 250);
+                if (!args.Requeue)
+                    break;
+
+                if (retryCount == 0)
+                    Interlocked.Increment(ref m_requeuedFileCount);
+
+                retryCount++;
+                int delay = GetDelay();
+                try { await Task.Delay(delay, cancellationToken); }
+                catch (TaskCanceledException) { }
+            }
+
+            if (retryCount > 0)
+            {
+                int requeuedFileCount = Interlocked.Decrement(ref m_requeuedFileCount);
+
+                if (requeuedFileCount == 0)
+                    m_requeueTokenSource.Dispose();
+            }
         }

         // Attempts to processes the given file.
@@ -1339,13 +1071,6 @@
                 return;

             OnProcessing(args);
-
-            // If the user requests to requeue the file,
-            // we don't consider the file processed yet
-            if (args.Requeue)
-                return;
-
-            Interlocked.Increment(ref m_processedFileCount);
         }

         // Triggers the processing event for the given file.
@@ -1357,8 +1082,7 @@
         // Triggers the error event with the given exception.
         private void OnError(Exception ex)
         {
-            if ((object)Error != null)
-                Error(this, new ErrorEventArgs(ex));
+            Error?.Invoke(this, new ErrorEventArgs(ex));
         }

         // Queues the created file for processing.
@@ -1368,13 +1092,15 @@

             m_watcherThread.Push(() =>
             {
+                DateTime lastWriteTime = File.GetLastWriteTimeUtc(fullPath);
+
                 if (!MatchesFilter(fullPath))
                 {
-                    m_processingThread.Push(1, () => TouchAndSkip(fullPath));
+                    m_processingThread.Push(1, () => TouchAndSkip(fullPath, lastWriteTime));
                     return;
                 }

-                m_processingThread.Push(2, () => TouchAndProcess(fullPath, true));
+                m_processingThread.Push(2, () => TouchAndProcess(fullPath, lastWriteTime, true));
             });
         }

@@ -1388,16 +1114,18 @@

             m_watcherThread.Push(() =>
             {
+                DateTime lastWriteTime = File.GetLastWriteTimeUtc(fullPath);
+
                 if (!MatchesFilter(fullPath))
                 {
-                    m_processingThread.Push(1, () => TouchAndSkip(fullPath));
+                    m_processingThread.Push(1, () => TouchAndSkip(fullPath, lastWriteTime));
                     return;
                 }

                 m_processingThread.Push(2, () =>
                 {
                     m_touchedFiles.Remove(fullPath);
-                    TouchAndProcess(fullPath, true);
+                    TouchAndProcess(fullPath, lastWriteTime, true);
                 });
             });
         }
@@ -1410,6 +1138,7 @@

             m_watcherThread.Push(() =>
             {
+                DateTime lastWriteTime = File.GetLastWriteTimeUtc(fullPath);
                 bool matchesFilter = MatchesFilter(fullPath);
                 int priority = matchesFilter ? 2 : 1;

@@ -1422,9 +1151,9 @@
                     }

                     if (matchesFilter)
-                        TouchAndProcess(fullPath, true);
+                        TouchAndProcess(fullPath, lastWriteTime, true);
                     else
-                        TouchAndSkip(fullPath);
+                        TouchAndSkip(fullPath, lastWriteTime);
                 });
             });
         }
@@ -1439,8 +1168,7 @@
         // Triggers the error event for the exception encountered by the file watcher.
         private void Watcher_Error(object sender, ErrorEventArgs args)
         {
-            if ((object)Error != null)
-                Error(sender, args);
+            Error?.Invoke(sender, args);
         }

         // Picks up files that were missed by the file watchers.


Add test for ThrottledStream.Write.

Add test for ThrottledStream.Write.

--- Duplicati/UnitTest/UtilityTests.cs
+++ Duplicati/UnitTest/UtilityTests.cs
@@ -21,6 +21,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Globalization;
+using System.IO;

 namespace Duplicati.UnitTest
 {
@@ -204,6 +205,38 @@
                 Assert.IsFalse(Utility.ParseBool(value, returnsFalse));
             }
         }
+
+        [Test]
+        [Category("Utility")]
+        public static void ThrottledStreamWrite()
+        {
+            byte[] initialBuffer = {0x10, 0x20, 0x30, 0x40, 0x50};
+            byte[] source = {0x60, 0x70, 0x80, 0x90};
+            const int offset = 1;
+            const int bytesToWrite = 3;
+
+            using (MemoryStream baseStream = new MemoryStream(initialBuffer))
+            {
+                const int readSpeed = 1;
+                const int writeSpeed = 1;
+
+                ThrottledStream throttledStream = new ThrottledStream(baseStream, readSpeed, writeSpeed);
+                throttledStream.Write(source, offset, bytesToWrite);
+
+                byte[] result = baseStream.ToArray();
+                for (int k = 0; k < result.Length; k++)
+                {
+                    if (k < bytesToWrite)
+                    {
+                        Assert.AreEqual(source[offset + k], result[k]);
+                    }
+                    else
+                    {
+                        Assert.AreEqual(initialBuffer[k], result[k]);
+                    }
+                }
+            }
+        }
     }

     /// <summary>