Fixup retry logic as requests cannot be reused

--- TMDbLib/Rest/RestRequest.cs
+++ TMDbLib/Rest/RestRequest.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Net;
 using System.Net.Http;
 using System.Net.Http.Headers;
@@ -136,8 +137,7 @@

         public async Task<RestResponse> ExecuteGet()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Get);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Get);

             CheckResponse(resp);

@@ -146,8 +146,7 @@

         public async Task<RestResponse<T>> ExecuteGet<T>()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Get);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Get);

             CheckResponse(resp);

@@ -156,8 +155,7 @@

         public async Task<RestResponse> ExecutePost()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Post);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Post);

             CheckResponse(resp);

@@ -166,8 +164,7 @@

         public async Task<RestResponse<T>> ExecutePost<T>()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Post);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Post);

             CheckResponse(resp);

@@ -176,8 +173,7 @@

         public async Task<RestResponse> ExecuteDelete()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Delete);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Delete);

             CheckResponse(resp);

@@ -186,49 +182,57 @@

         public async Task<RestResponse<T>> ExecuteDelete<T>()
         {
-            HttpRequestMessage req = PrepRequest(HttpMethod.Delete);
-            HttpResponseMessage resp = await SendInternal(req);
+            HttpResponseMessage resp = await SendInternal(HttpMethod.Delete);

             CheckResponse(resp);

             return new RestResponse<T>(resp);
         }

-        private async Task<HttpResponseMessage> SendInternal(HttpRequestMessage req)
-        {
-            HttpClient client = new HttpClient();
-
+        private async Task<HttpResponseMessage> SendInternal(HttpMethod method)
+        {
             // Account for the following settings:
             // - MaxRetryCount                          Max times to retry
             // DEPRECATED RetryWaitTimeInSeconds        Time to wait between retries
             // DEPRECATED ThrowErrorOnExeedingMaxCalls  Throw an exception if we hit a ratelimit

-            HttpResponseMessage resp = null;
-            for (int i = 0; i <= _client.MaxRetryCount; i++)
-            {
-                if (resp != null && resp.StatusCode == (HttpStatusCode)429)
+            int timesToTry = _client.MaxRetryCount + 1;
+
+            Debug.Assert(timesToTry >= 1);
+
+            do
+            {
+                HttpRequestMessage req = PrepRequest(method);
+                HttpResponseMessage resp = await new HttpClient().SendAsync(req);
+
+                if (resp.StatusCode == (HttpStatusCode)429)
                 {
                     // The previous result was a ratelimit, read the Retry-After header and wait the allotted time
-                    RetryConditionHeaderValue retryAfter = resp.Headers.RetryAfter;
-                    TimeSpan timeToWait = (retryAfter?.Delta.Value ?? (TimeSpan?)TimeSpan.FromSeconds(5)).Value;
-
-                    await Task.Delay(timeToWait);
+                    TimeSpan? retryAfter = resp.Headers.RetryAfter?.Delta.Value;
+
+                    if (retryAfter.HasValue && retryAfter.Value.TotalSeconds > 0)
+                        await Task.Delay(retryAfter.Value);
+                    else
+                        // TMDb sometimes gives us 0-second waits, which can lead to rapid succession of requests
+                        await Task.Delay(TimeSpan.FromSeconds(1));
+
+                    continue;
                 }

-                resp = await client.SendAsync(req);
-
                 if (resp.IsSuccessStatusCode)
-                    // We have a success
-                    break;
-            }
-
-            if (resp == null || !resp.IsSuccessStatusCode)
-            {
-                // We never reached a success
-                throw new RequestLimitExceededException();
-            }
-
-            return resp;
+                {
+                    return resp;
+                }
+
+                if (!resp.IsSuccessStatusCode)
+                    return resp;
+                //// We had a web exception - throw one
+                //throw new WebserverException(resp.StatusCode);
+
+            } while (timesToTry-- > 0);
+
+            // We never reached a success
+            throw new RequestLimitExceededException();
         }
     }
 }


CustomBlocks: FastLZBlockCompressor class updated, added optional (slower) thread safe approach

--- CustomBlocks/Compression/FastLZ/FastLZBlockCompressor.cs
+++ CustomBlocks/Compression/FastLZ/FastLZBlockCompressor.cs
@@ -1,4 +1,4 @@
-// FastLZBlockCompressor.cs
+// FastLZBlockCompressor.cs
 //
 // The MIT License (MIT)
 //
@@ -27,20 +27,33 @@

 namespace DarkCaster.Compression.FastLZ
 {
-	public sealed class FastLZBlockCompressor : IBlockCompressor
+	public sealed class FastLZBlockCompressor : IBlockCompressor, IThreadSafeBlockCompressor
 	{
 		private const int PAYLOAD_LEN1 = 31; //2 ^ 5 - 1 = 5 bits / 8 bit-header;
 		private const int PAYLOAD_LEN2 = 8191; //2 ^ 13 - 1 = 13 bits / 16 bit-header;
 		private const int PAYLOAD_LEN3 = 2097151; //2 ^ 21 - 1 = 21 bits / 24 bit-header;
 		private const int MAX_BLOCK_SZ = 536870911; //2 ^ 29 - 1 = 29 bits / 32-bit header;
-		private readonly FastLZ fastLZ = new FastLZ();
 		private readonly int maxBlockSz;

-		public FastLZBlockCompressor(int maxBlockSz=MAX_BLOCK_SZ)
+		private readonly Func<byte[], int, int, byte[], int, int> fastLZDecompress;
+		private readonly Func<byte[], int, int, byte[], int, int> fastLZCompress;
+
+		public FastLZBlockCompressor(int maxBlockSz = MAX_BLOCK_SZ, bool useThreadSafeApproach = false)
 		{
 			if (maxBlockSz > MAX_BLOCK_SZ || maxBlockSz < 0)
 				throw new ArgumentException("maxBlockSz is too big or < 0 ! Upper limit is " + MAX_BLOCK_SZ.ToString(), nameof(maxBlockSz));
 			this.maxBlockSz = maxBlockSz;
+			if(useThreadSafeApproach)
+			{
+				fastLZCompress = FastLZStatic.Compress;
+				fastLZDecompress = FastLZStatic.Decompress;
+			}
+			else
+			{
+				var fastLZ = new FastLZ();
+				fastLZCompress = fastLZ.Compress;
+				fastLZDecompress = fastLZ.Decompress;
+			}
 		}

 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -88,7 +101,7 @@
 			if (inSz > maxBlockSz)
 				throw new ArgumentException("inSz parameter too big, maximum supported input block size is " + maxBlockSz.ToString(), nameof(inSz));
 			var hdrSz=CalculateHeaderLength(inSz);
-			var comprSz = inSz > 15 ? fastLZ.Compress(input, inOffset, inSz, output, outOffset + hdrSz) : inSz;
+			var comprSz = inSz > 15 ? fastLZCompress(input, inOffset, inSz, output, outOffset + hdrSz) : inSz;
 			bool useCompr = true;
 			if (comprSz >= inSz)
 			{
@@ -100,16 +113,21 @@
 			return comprSz + hdrSz;
 		}

+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		private static int ReadMetadataSZ(byte[] buffer, int offset = 0)
+		{
+			return (buffer[offset] >> 5 & 0X3) + 1;
+		}
+
 		public int DecodeComprBlockSZ(byte[] buffer, int offset=0)
 		{
-			var hdrLen = DecodeMetadataSZ(buffer, offset);
+			var hdrLen = ReadMetadataSZ(buffer, offset);
 			return DecodeComprDataSz(buffer, offset, hdrLen) + hdrLen;
 		}

-		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		public int DecodeMetadataSZ(byte[] buffer, int offset=0)
 		{
-			return (buffer[offset] >> 5 & 0X3) + 1;
+			return ReadMetadataSZ(buffer, offset);
 		}

 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -138,7 +156,7 @@
 				Buffer.BlockCopy(input, inOffset + headerSz, output, outOffset, comprSz);
 				return comprSz;
 			}
-			return fastLZ.Decompress(input, inOffset + headerSz, comprSz, output, outOffset);
+			return fastLZDecompress(input, inOffset + headerSz, comprSz, output, outOffset);
 		}

 		public int MaxBlockSZ { get { return maxBlockSz; } }


Added a test case to verify correct calendar serialization/loading when different (custom) calendar types are used

--- DDay.iCal.Test/Serialize.cs
+++ DDay.iCal.Test/Serialize.cs
@@ -47,6 +47,7 @@
             s.SERIALIZE14();
             s.SERIALIZE15();
             s.SERIALIZE16();
+            s.SERIALIZE17();
             s.LANGUAGE1();
             s.LANGUAGE2();
             s.REQUIREDPARAMETERS1();
@@ -56,16 +57,17 @@
             s.USHOLIDAYS();
         }

-        private void SerializeTest(string filename)
-        {
-            iCalendar iCal1 = iCalendar.LoadFromFile(@"Calendars\Serialization\" + filename);
+        private void SerializeTest(string filename) { SerializeTest(filename, typeof(iCalendar)); }
+        private void SerializeTest(string filename, Type iCalType)
+        {
+            iCalendar iCal1 = iCalendar.LoadFromFile(iCalType, @"Calendars\Serialization\" + filename);
             iCalendarSerializer serializer = new iCalendarSerializer(iCal1);

             if (!Directory.Exists(@"Calendars\Serialization\Temp"))
                 Directory.CreateDirectory(@"Calendars\Serialization\Temp");

             serializer.Serialize(@"Calendars\Serialization\Temp\" + Path.GetFileNameWithoutExtension(filename) + "_Serialized.ics");
-            iCalendar iCal2 = iCalendar.LoadFromFile(@"Calendars\Serialization\Temp\" + Path.GetFileNameWithoutExtension(filename) + "_Serialized.ics");
+            iCalendar iCal2 = iCalendar.LoadFromFile(iCalType, @"Calendars\Serialization\Temp\" + Path.GetFileNameWithoutExtension(filename) + "_Serialized.ics");

             CompareCalendars(iCal1, iCal2);
         }
@@ -245,7 +247,23 @@
             foreach (CustomEvent1 evt1 in iCal.Events)
                 Assert.IsTrue(evt1.NonstandardProperty.Equals(nonstandardText));

-            SerializeTest("SERIALIZE16.ics");
+            SerializeTest("SERIALIZE16.ics", typeof(CustomICal1));
+        }
+
+        [Test, Category("Serialization")]
+        public void SERIALIZE17()
+        {
+            // Create a normal iCalendar, serialize it, and load it as a custom calendar
+            iCalendar iCal = new iCalendar();
+
+            Event evt = Event.Create(iCal);
+            evt.Summary = "Test event";
+            evt.Start = new DateTime(2007, 02, 15, 8, 0, 0);
+
+            iCalendarSerializer serializer = new iCalendarSerializer(iCal);
+            serializer.Serialize(@"Calendars\Serialization\SERIALIZE17.ics");
+
+            SerializeTest("SERIALIZE17.ics", typeof(CustomICal1));
         }

         [Test, Category("Serialization")]


Fix to parse exponential values

Fix to parse exponential values

git-tfs-id: [https://tfs.codeplex.com/tfs/TFS01]$/SharpMap/Trunk;C63775

--- SharpMap/Converters/WellKnownText/StreamTokenizer.cs
+++ SharpMap/Converters/WellKnownText/StreamTokenizer.cs
@@ -224,11 +224,23 @@
                 }

                 // handle negative numbers
-                if (currentCharacter == '-' && nextTokenType == TokenType.Number && isNumber == false)
+                if (currentCharacter == '-' && nextTokenType == TokenType.Number) // && isNumber == false)
                 {
                     _currentTokenType = TokenType.Number;
                     nextTokenType = TokenType.Number;
                     //isNumber = true;
+                }
+
+                // this handles numbers with exponential values
+                if (isNumber && (nextCharacter.Equals('E') || nextCharacter.Equals('e')))
+                {
+                    nextTokenType = TokenType.Number;
+                }
+
+                if (isNumber && (currentCharacter.Equals('E') || currentCharacter.Equals('e')) && (nextTokenType == TokenType.Number || nextTokenType == TokenType.Symbol))
+                {
+                    _currentTokenType = TokenType.Number;
+                    nextTokenType = TokenType.Number;
                 }


More work on the desk band, core implementation is close to being done, just need a way to have the derived class supply a form, update the registration and write some tests.

--- SharpShell/SharpShell/SharpDeskBand/SharpDeskBand.cs
+++ SharpShell/SharpShell/SharpDeskBand/SharpDeskBand.cs
@@ -10,6 +10,9 @@
     [ServerType(ServerType.ShellDeskBand)]
     public class SharpDeskBand : SharpShellServer, IDeskBand, IPersistStream, IObjectWithSite
     {
+        //  TODO: Implement IContextMenu
+        //  TODO: Implement IInputObject
+
         /// <summary>
         /// The COM site (see IObjectWithSite implementation).
         /// </summary>
@@ -145,10 +148,54 @@
             explorerBandId = dwBandID;

             //  Depending on what we've been asked for, we'll return various band properties.
+
+            //  Return the min size if needed.
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_MINSIZE))
+            {
+                //  TODO: provide minsize.
+                pdbi.ptMinSize.X = 200;
+                pdbi.ptMinSize.Y = 30;
+            }
+
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_MAXSIZE))
+            {
+                //  TODO: check documentation for this.
+                pdbi.ptMaxSize.Y = -1;
+            }
+
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_INTEGRAL))
+            {
+                //  TODO: check documentation for this.
+                pdbi.ptIntegral.Y = 1;
+            }
+
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_ACTUAL))
+            {
+                //  TODO: Return actual size.
+                pdbi.ptActual.X = 200;
+                pdbi.ptActual.Y = 30;
+            }
+
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_TITLE))
+            {
+                //  TODO: Handle titles.
+                // Don't show title by removing this flag.
+                pdbi.dwMask &= ~DBIM.DBIM_TITLE;
+            }
+
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_MODEFLAGS))
+            {
+                //  TODO: Handle flags.
+                pdbi.dwModeFlags = DBIMF.DBIMF_NORMAL | DBIMF.DBIMF_VARIABLEHEIGHT;
+            }

-            //  TODO: Desk bands: Check the flags and set the struct accordingly.
-            //  TODO: Desk bands: Allow implementers to specify the flags in the best way.
-
+            if (pdbi.dwMask.HasFlag(DBIM.DBIM_BKCOLOR))
+            {
+                //  TODO: Handle background colour.
+                // Use the default background color by removing this flag.
+                pdbi.dwMask &= ~DBIM.DBIM_BKCOLOR;
+            }
+
             //  Return success.
             return WinError.S_OK;
         }


clean

clean

--- Demos/GridViewer/Program.cs
+++ Demos/GridViewer/Program.cs
@@ -18,8 +18,8 @@
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             var mainWindow = new FormMain();
-            Rectangle rect = WindowSizeHelper.WindowSize(0.8f, 0.9f);
-            mainWindow.Size = rect.Size;
+            //Rectangle rect = WindowSizeHelper.WindowSize(0.8f, 0.9f);
+            //mainWindow.Size = rect.Size;
             mainWindow.StartPosition = FormStartPosition.CenterScreen;
             Application.Run(mainWindow);
         }


Minor parameter name change.

Minor parameter name change.

--- ECS/Entities/AtlasEntity.cs
+++ ECS/Entities/AtlasEntity.cs
@@ -86,7 +86,7 @@
 			get => globalName;
 			set
 			{
-				if(!IsValidName(globalName, ref value, s => Engine?.HasEntity(s) ?? false))
+				if(!IsValidName(globalName, ref value, n => Engine?.HasEntity(n) ?? false))
 					return;
 				string previous = globalName;
 				globalName = value;
@@ -99,7 +99,7 @@
 			get => localName;
 			set
 			{
-				if(!IsValidName(localName, ref value, s => Parent?.HasChild(s) ?? false))
+				if(!IsValidName(localName, ref value, n => Parent?.HasChild(n) ?? false))
 					return;
 				string previous = localName;
 				localName = value;


Removed unnecessary shim.

Removed unnecessary shim.

--- src/dotnet-test-nunit/Extensions/SafeAttributeAccess.cs
+++ src/dotnet-test-nunit/Extensions/SafeAttributeAccess.cs
@@ -21,14 +21,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 // ***********************************************************************

-using System;
 using System.Xml;
-
-namespace System.Runtime.CompilerServices
-{
-    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
-    sealed class ExtensionAttribute : Attribute { }
-}

 namespace NUnit.Engine.Listeners
 {


Add mutability test to StripNamespaces

Add mutability test to StripNamespaces

--- ExtensionsTests/XmlTests.cs
+++ ExtensionsTests/XmlTests.cs
@@ -27,6 +27,7 @@
             var xCustomer = stripped.Element("customer");
             var xHello = xCustomer?.Attribute("hello");

+            Assert.AreNotSame(xml, stripped);
             Assert.IsNotNull(xCustomer);
             Assert.IsNotNull(xHello);
             Assert.AreEqual("world", xHello.Value);


Fix: removing async operator from non-async action.

--- UI/Services/AssetService.cs
+++ UI/Services/AssetService.cs
@@ -46,7 +46,7 @@
                 var originalParent = assetToMove.Parent;
                 var newPath = originalPath;

-                var undoCommand = new UndoCommand(async () => {
+                var undoCommand = new UndoCommand(() => {
                     assetToMove.Parent = newParent;
                     newPath = assetToMove.GetPath();
                     this.MoveAsset(assetToMove, originalPath);


Set LastPlayTime of new shortcuts to now

This makes so that when new apps are exported to Steam, they actually show at the top of the library, instead of the very bottom.

--- UWPHook/GamesWindow.xaml.cs
+++ UWPHook/GamesWindow.xaml.cs
@@ -169,7 +169,10 @@
                                     IsHidden = 0,
                                     OpenVR = 0,
                                     ShortcutPath = "",
-                                    Tags = new string[0]
+                                    Tags = new string[0],
+                                    LaunchOptions = "",
+                                    AllowOverlay = 1,
+                                    LastPlayTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                                 };

                                 //Resize this array so it fits the new entries