Log export errors to a text file next to the output file.

--- src/SolidGui/Export/ExportLift.cs
+++ src/SolidGui/Export/ExportLift.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.IO;
 using System.Text;
 using Palaso.DictionaryServices.Lift;
 using Palaso.DictionaryServices.Model;
@@ -17,87 +18,110 @@
         {
             var index = new Dictionary<string, SfmLiftLexEntryAdapter>();
             var liftLexEntries = new List<SfmLiftLexEntryAdapter>();
-
+            var logPath = outputFilePath+ ".exportErrors.txt";
+            if(File.Exists(logPath))
+            {
+                File.Delete(logPath);
+            }
             using (var dm = new LiftDataMapper(outputFilePath))
             {
                 // first pass
                 foreach (var sfmLexEntry in sfmLexEntries)
                 {
-                    var adaptedEntry = new SfmLiftLexEntryAdapter(dm, sfmLexEntry.LexEntry, solidSettings);
-                    liftLexEntries.Add(adaptedEntry);
-                    adaptedEntry.PopulateEntry();
+                    try
+                    {
+                        var adaptedEntry = new SfmLiftLexEntryAdapter(dm, sfmLexEntry.LexEntry, solidSettings);
+                        liftLexEntries.Add(adaptedEntry);
+                        adaptedEntry.PopulateEntry();

-                    if (index.ContainsKey(adaptedEntry.SfmID)) // is a duplicate
+                        if (index.ContainsKey(adaptedEntry.SfmID)) // is a duplicate
+                        {
+                            // get the duplicated entry from index and change its name to SfmID_HomonymNumber
+                            var entry = index[adaptedEntry.SfmID];
+                            entry.SfmID = entry.SfmID + "_" + entry.HomonymNumber;
+
+                            // add the new adapted entry to the index with name SfmID_HomonymNumber
+                            index.Add(adaptedEntry.SfmID + "_" + adaptedEntry.HomonymNumber, adaptedEntry);
+                        }
+                        else
+                        {
+                            // add to dictionary
+                            index.Add(adaptedEntry.SfmID, adaptedEntry);
+                        }
+                    }
+                    catch (Exception error)
                     {
-                        // get the duplicated entry from index and change its name to SfmID_HomonymNumber
-                        var entry = index[adaptedEntry.SfmID];
-                        entry.SfmID = entry.SfmID + "_" + entry.HomonymNumber;
-
-                        // add the new adapted entry to the index with name SfmID_HomonymNumber
-                        index.Add(adaptedEntry.SfmID + "_" + adaptedEntry.HomonymNumber, adaptedEntry);
+                        using (var log = System.IO.File.AppendText(logPath))
+                        {
+                            log.Write(sfmLexEntry.LexEntry.Name+": ");
+                            log.WriteLine(error);
+                        }
                     }
-                    else
-                    {
-                        // add to dictionary
-                        index.Add(adaptedEntry.SfmID, adaptedEntry);
-                    }
-
-
                 }
-
                 SfmLiftLexEntryAdapter targetLexEntry;

                 // second pass
                 foreach (var adaptedEntry in liftLexEntries)
                 {
-                    foreach (var relation in adaptedEntry.Relations)
+                    try
                     {
-                        // if the target is in the dictionary, add the relation, else post a note
-                        if (index.ContainsKey(relation.TargetID))
-                        {
-                            targetLexEntry = index[relation.TargetID];
-                            string guidOfTargetEntry = targetLexEntry.GUID;
-                            adaptedEntry.MakeRelation(guidOfTargetEntry, relation.Type);
-
-                        }
-                        else
-                        {
-                            // add an error note
-                            adaptedEntry.AddSolidNote(String.Format("Cannot find target: {0}", relation.TargetID));
-                            // add a note
-                            //adaptedEntry.LiftLexEntry.
-                        }
-                    }
-
-                    dm.SaveItem(adaptedEntry.LiftLexEntry);
-
-                    foreach (var subEntry in adaptedEntry.SubEntries)
-                    {
-                        foreach (var relation in subEntry.Relations)
+                        foreach (var relation in adaptedEntry.Relations)
                         {
                             // if the target is in the dictionary, add the relation, else post a note
                             if (index.ContainsKey(relation.TargetID))
                             {
                                 targetLexEntry = index[relation.TargetID];
                                 string guidOfTargetEntry = targetLexEntry.GUID;
-                                adaptedEntry.MakeRelation(guidOfTargetEntry, relation.Type);
+                                adaptedEntry.MakeRelation(guidOfTargetEntry, relation.Type);

                             }
                             else
                             {
+                                // add an error note
+                                adaptedEntry.AddSolidNote(String.Format("Cannot find target: {0}", relation.TargetID));
                                 // add a note
                                 //adaptedEntry.LiftLexEntry.
                             }
                         }

-                        dm.SaveItem(subEntry.LiftLexEntry);
+                        dm.SaveItem(adaptedEntry.LiftLexEntry);
+
+                        foreach (var subEntry in adaptedEntry.SubEntries)
+                        {
+                            foreach (var relation in subEntry.Relations)
+                            {
+                                // if the target is in the dictionary, add the relation, else post a note
+                                if (index.ContainsKey(relation.TargetID))
+                                {
+                                    targetLexEntry = index[relation.TargetID];
+                                    string guidOfTargetEntry = targetLexEntry.GUID;
+                                    adaptedEntry.MakeRelation(guidOfTargetEntry, relation.Type);
+
+                                }
+                                else
+                                {
+                                    // add a note
+                                    //adaptedEntry.LiftLexEntry.
+                                }
+                            }
+
+                            dm.SaveItem(subEntry.LiftLexEntry);
+                        }
+
+                        // Got any potential relations?
+                        // if so
+                        // foreach one find your target in the index, get it's id, and make the relation
+
+
                     }
-
-                    // Got any potential relations?
-                    // if so
-                    // foreach one find your target in the index, get it's id, and make the relation
-
-
+                    catch (Exception error)
+                    {
+                        using (var log = System.IO.File.AppendText(logPath))
+                        {
+                            log.Write(adaptedEntry.SfmID + ": ");
+                            log.WriteLine(error);
+                        }
+                    }
                 }
             }


aggreegate -> aggregate

aggreegate -> aggregate

--- src/TraceEvent/Stacks/CallTree.cs
+++ src/TraceEvent/Stacks/CallTree.cs
@@ -2031,7 +2031,7 @@
         /// <summary>
         /// AggregateCallTreeNode can represent either a 'callers' tree or a 'callees' tree.   For
         /// the 'callers' tree case the node represented by the aggregate does NOT have same ID as
-        /// the tree in the m_trees list.   Instead the aggreegate is some node 'up the chain' toward
+        /// the tree in the m_trees list.   Instead the aggregate is some node 'up the chain' toward
         /// the caller.  m_callerOffset keeps track of this (it is the same number for all elements
         /// in m_trees).
         ///


Add proper android touch action handler

Add proper android touch action handler

--- osu.Framework.Android/Input/AndroidTouchHandler.cs
+++ osu.Framework.Android/Input/AndroidTouchHandler.cs
@@ -1,9 +1,12 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.

+using System;
 using Android.Views;
+using osu.Framework.Input;
 using osu.Framework.Input.Handlers;
 using osu.Framework.Input.StateChanges;
+using osu.Framework.Input.States;
 using osu.Framework.Platform;
 using osuTK;
 using osuTK.Input;
@@ -14,56 +17,63 @@
     {
         private readonly AndroidGameView view;

+        public override bool IsActive => true;
+
+        public override int Priority => 0;
+
         public AndroidTouchHandler(AndroidGameView view)
         {
             this.view = view;
-            view.Touch += handleTouches;
+            view.Touch += handleTouch;
             view.Hover += handleHover;
         }

-        private void handleTouches(object sender, View.TouchEventArgs e)
+        public override bool Initialize(GameHost host) => true;
+
+        private void handleTouch(object sender, View.TouchEventArgs e)
         {
-            handlePointerEvent(e.Event);
+            if (e.Event.Action == MotionEventActions.Move)
+            {
+                for (int i = 0; i < Math.Min(e.Event.PointerCount, TouchState.MAX_TOUCH_COUNT); i++)
+                {
+                    var touch = getEventTouch(e.Event, i);
+                    PendingInputs.Enqueue(new TouchInput(touch, true));
+                }
+            }
+            else if (e.Event.ActionIndex < TouchState.MAX_TOUCH_COUNT)
+            {
+                var touch = getEventTouch(e.Event, e.Event.ActionIndex);

-            switch (e.Event.Action & MotionEventActions.Mask)
-            {
-                case MotionEventActions.Down:
-                case MotionEventActions.Move:
-                    PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Left, true));
-                    break;
+                switch (e.Event.ActionMasked)
+                {
+                    case MotionEventActions.Down:
+                    case MotionEventActions.PointerDown:
+                        PendingInputs.Enqueue(new TouchInput(touch, true));
+                        break;

-                case MotionEventActions.Up:
-                    PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Left, false));
-                    break;
+                    case MotionEventActions.Up:
+                    case MotionEventActions.PointerUp:
+                    case MotionEventActions.Cancel:
+                        PendingInputs.Enqueue(new TouchInput(touch, false));
+                        break;
+                }
             }
         }

         private void handleHover(object sender, View.HoverEventArgs e)
         {
-            handlePointerEvent(e.Event);
-
+            PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = getEventPosition(e.Event) });
             PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Right, e.Event.ButtonState == MotionEventButtonState.StylusPrimary));
         }

-        private void handlePointerEvent(MotionEvent e)
-        {
-            PendingInputs.Enqueue(new MousePositionAbsoluteInput
-            {
-                Position = new Vector2(e.GetX() * view.ScaleX, e.GetY() * view.ScaleY)
-            });
-        }
+        private Touch getEventTouch(MotionEvent e, int index) => new Touch((TouchSource)e.GetPointerId(index), getEventPosition(e, index));
+        private Vector2 getEventPosition(MotionEvent e, int index = 0) => new Vector2(e.GetX(index) * view.ScaleX, e.GetY(index) * view.ScaleY);

         protected override void Dispose(bool disposing)
         {
-            view.Touch -= handleTouches;
+            view.Touch -= handleTouch;
             view.Hover -= handleHover;
             base.Dispose(disposing);
         }
-
-        public override bool IsActive => true;
-
-        public override int Priority => 0;
-
-        public override bool Initialize(GameHost host) => true;
     }
 }


Move Rankings property below ctor

Move Rankings property below ctor

--- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
+++ osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
@@ -23,23 +23,6 @@
         private readonly int page;
         private readonly FillFlowContainer backgroundFlow;

-        public IReadOnlyList<TModel> Rankings
-        {
-            set
-            {
-                Content = null;
-                backgroundFlow.Clear();
-
-                if (value?.Any() != true)
-                    return;
-
-                value.ForEach(_ => backgroundFlow.Add(new TableRowBackground()));
-
-                Columns = CreateHeaders();
-                Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular();
-            }
-        }
-
         protected RankingsTable(int page)
         {
             this.page = page;
@@ -56,6 +39,23 @@
                 Depth = 1f,
                 Margin = new MarginPadding { Top = row_height }
             });
+        }
+
+        public IReadOnlyList<TModel> Rankings
+        {
+            set
+            {
+                Content = null;
+                backgroundFlow.Clear();
+
+                if (value?.Any() != true)
+                    return;
+
+                value.ForEach(_ => backgroundFlow.Add(new TableRowBackground()));
+
+                Columns = CreateHeaders();
+                Content = value.Select((s, i) => CreateContent(page * items_per_page - (items_per_page - i), s)).ToArray().ToRectangular();
+            }
         }

         protected abstract TableColumn[] CreateHeaders();


Fix PlayerSettingsOverlay not reappearing when Hidden

--- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs
+++ osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs
@@ -49,6 +49,9 @@
         protected override void PopIn() => this.FadeIn(fade_duration);
         protected override void PopOut() => this.FadeOut(fade_duration);

+        //We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible
+        public override bool HandleKeyboardInput => true;
+
         protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
         {
             if (args.Repeat) return false;


Define ResizableLimits.ToString

Define ResizableLimits.ToString

--- libwasm/ResizableLimits.cs
+++ libwasm/ResizableLimits.cs
@@ -90,5 +90,13 @@
             }
             Writer.Write("}");
         }
+
+        /// <inheritdoc/>
+        public override string ToString()
+        {
+            var builder = new StringBuilder();
+            Dump(new StringWriter(builder));
+            return builder.ToString();
+        }
     }
 }


Uploaded the code.

Uploaded the code.


[V2] Adding a source file to an object file collection can now specify which module the macros reside in

--- packages/C/dev/Scripts/ObjectFileCollectionBase.cs
+++ packages/C/dev/Scripts/ObjectFileCollectionBase.cs
@@ -27,12 +27,16 @@
     {
         private System.Collections.Generic.List<ChildModuleType> children = new System.Collections.Generic.List<ChildModuleType>();

-        public ChildModuleType AddFile(string path)
+        public ChildModuleType
+        AddFile(
+            string path,
+            Bam.Core.V2.Module macroModuleOverride = null)
         {
             // TODO: how can I distinguish between creating a child module that inherits it's parents settings
             // and from a standalone object of type ChildModuleType which should have it's own copy of the settings?
             var child = Bam.Core.V2.Module.Create<ChildModuleType>(this);
-            child.InputPath = Bam.Core.V2.TokenizedString.Create(path, this);
+            var macroModule = (macroModuleOverride == null) ? this : macroModuleOverride;
+            child.InputPath = Bam.Core.V2.TokenizedString.Create(path, macroModule);
             (child as Bam.Core.V2.IChildModule).Parent = this;
             this.children.Add(child);
             this.DependsOn(child);


Update AMDPCNetII.cs

Update AMDPCNetII.cs

--- source/Cosmos.HAL2/Drivers/PCI/Network/AMDPCNetII.cs
+++ source/Cosmos.HAL2/Drivers/PCI/Network/AMDPCNetII.cs
@@ -4,7 +4,6 @@
 using Cosmos.Core;
 using Cosmos.Core.IOGroup.Network;
 using Cosmos.HAL.Network;
-using IL2CPU.API.Attribs;

 namespace Cosmos.HAL.Drivers.PCI.Network
 {
@@ -36,7 +35,7 @@
             this.pciCard.Claimed = true;
             this.pciCard.EnableDevice();

-            this.io = new AMDPCNetIIIOGroup((ushort) this.pciCard.BaseAddressBar[0].BaseAddress);
+            this.io = new AMDPCNetIIIOGroup((ushort)this.pciCard.BaseAddressBar[0].BaseAddress);
             this.io.RegisterData.DWord = 0;

             // Get the EEPROM MAC Address and set it as the devices MAC
@@ -58,8 +57,8 @@

             mInitBlock.Write32(0x00, (0x4 << 28) | (0x4 << 20));
             mInitBlock.Write32(0x04,
-                (UInt32) (eeprom_mac[0] | (eeprom_mac[1] << 8) | (eeprom_mac[2] << 16) | (eeprom_mac[3] << 24)));
-            mInitBlock.Write32(0x08, (UInt32) (eeprom_mac[4] | (eeprom_mac[5] << 8)));
+                (UInt32)(eeprom_mac[0] | (eeprom_mac[1] << 8) | (eeprom_mac[2] << 16) | (eeprom_mac[3] << 24)));
+            mInitBlock.Write32(0x08, (UInt32)(eeprom_mac[4] | (eeprom_mac[5] << 8)));
             mInitBlock.Write32(0x0C, 0x0);
             mInitBlock.Write32(0x10, 0x0);
             mInitBlock.Write32(0x14, mRxDescriptor.Offset);
@@ -76,9 +75,9 @@

                 ManagedMemoryBlock buffer = new ManagedMemoryBlock(2048);
                 mRxDescriptor.Write32(xOffset + 8, buffer.Offset);
-                UInt16 buffer_len = (UInt16) (~buffer.Size);
+                UInt16 buffer_len = (UInt16)(~buffer.Size);
                 buffer_len++;
-                UInt32 flags = (UInt32) (buffer_len & 0x0FFF) | 0xF000 | 0x80000000;
+                UInt32 flags = (UInt32)(buffer_len & 0x0FFF) | 0xF000 | 0x80000000;
                 mRxDescriptor.Write32(xOffset + 4, flags);
                 mRxBuffers.Add(buffer);
             }
@@ -134,17 +133,17 @@
         /// </summary>
         public static void FindAll()
         {
-          Console.WriteLine("Scanning for AMD PCNetII cards...");
-          PCIDevice device = Cosmos.HAL.PCI.GetDevice(VendorID.AMD, DeviceID.PCNETII);
-          if (device != null)
-          {
-               AMDPCNetII nic = new AMDPCNetII((PCIDevice) device);
-
+            Console.WriteLine("Scanning for AMD PCNetII cards...");
+            PCIDevice device = Cosmos.HAL.PCI.GetDevice(VendorID.AMD, DeviceID.PCNETII);
+            if (device != null)
+            {
+                AMDPCNetII nic = new AMDPCNetII((PCIDevice)device);
+
                 Console.WriteLine("Found AMD PCNetII NIC on PCI " + device.bus + ":" + device.slot + ":" +
                                   device.function);
                 Console.WriteLine("NIC IRQ: " + device.InterruptLine);
                 Console.WriteLine("NIC MAC Address: " + nic.MACAddress.ToString());
-          }
+            }
         }

         #region Register Access Properties
@@ -220,7 +219,6 @@
             return true;
         }

-        [DebugStub(Off = true)]
         public override bool QueueBytes(byte[] buffer, int offset, int length)
         {
             byte[] data = new byte[length];
@@ -277,7 +275,6 @@

         #region Helper Functions

-        [DebugStub(Off = true)]
         protected bool SendBytes(ref byte[] aData)
         {
             int txd = mNextTXDesc++;
@@ -286,7 +283,7 @@
                 mNextTXDesc = 0;
             }

-            uint xOffset = (uint) (txd * 16);
+            uint xOffset = (uint)(txd * 16);
             UInt32 status = mTxDescriptor.Read32(xOffset + 4);
             if ((status & 0x80000000) == 0)
             {
@@ -295,11 +292,11 @@
                     mTxBuffers[txd][b] = aData[b];
                 }
                 //UInt16 buffer_len = (UInt16)(aData.Length < 64 ? 64 : aData.Length);
-                UInt16 buffer_len = (UInt16) aData.Length;
-                buffer_len = (UInt16) (~buffer_len);
+                UInt16 buffer_len = (UInt16)aData.Length;
+                buffer_len = (UInt16)(~buffer_len);
                 buffer_len++;

-                UInt32 flags = (UInt32) ((buffer_len) & 0x0FFF) | 0x0300F000 | 0x80000000;
+                UInt32 flags = (UInt32)((buffer_len) & 0x0FFF) | 0x0300F000 | 0x80000000;

                 mTxDescriptor.Write32(xOffset + 4, flags);

@@ -309,7 +306,6 @@
             return false;
         }

-        [DebugStub(Off = true)]
         private void ReadRawData()
         {
             uint status;
@@ -318,11 +314,11 @@

             for (int rxd = 0; rxd < 16; rxd++)
             {
-                uint xOffset = (uint) (rxd * 16);
+                uint xOffset = (uint)(rxd * 16);
                 status = mRxDescriptor.Read32(xOffset + 4);
                 if ((status & 0x80000000) == 0)
                 {
-                    recv_size = (UInt16) (mRxDescriptor[xOffset + 0] & 0xFFF);
+                    recv_size = (UInt16)(mRxDescriptor.Read32(xOffset + 0) & 0xFFF);
                     recv_data = new byte[recv_size];
                     for (uint b = 0; b < recv_size; b++)
                     {


修改config.xml的解析，使其支持xmlns

修改config.xml的解析，使其支持xmlns

--- xFaceLib/xFaceLib/runtime/XSystemConfiguration.cs
+++ xFaceLib/xFaceLib/runtime/XSystemConfiguration.cs
@@ -5,6 +5,7 @@
 using System.Xml;
 using System.Xml.Linq;
 using System.IO.IsolatedStorage;
+using System.Linq;

 namespace xFaceLib.runtime
 {
@@ -147,9 +148,18 @@
         {
             Stream xFaceXml = Application.GetResourceStream(new Uri("config.xml", UriKind.Relative)).Stream;
             XDocument doc = XDocument.Load(xFaceXml);
-            XElement xFace = doc.Root.Element("xFace");
+            XElement widgetElement = doc.Root;
+
+            var values = from results in widgetElement.Descendants()
+                         where results.Name.LocalName == "xFace"
+                         select results;
+            XElement xFace = values.FirstOrDefault();
             ParsexFaceInfo(xFace);
-            XElement packages = doc.Root.Element("pre_install_packages");
+
+            values = from results in widgetElement.Descendants()
+                         where results.Name.LocalName == "pre_install_packages"
+                         select results;
+            XElement packages = values.FirstOrDefault();
             LoadPreinstallApp(packages);
         }

@@ -187,13 +197,14 @@
         /// <returns>对应的value值</returns>
         private String parsePrefValue(XElement xFaceElement,String attrName)
         {
-            foreach (XElement itemnode in xFaceElement.Descendants("preference"))
-            {
-                string name = itemnode.Attribute("name").Value;
-                if (name.Equals(attrName))
-                {
-                    return itemnode.Attribute("value").Value;
-                }
+            var itemnodes = from results in xFaceElement.Descendants()
+                         where results.Name.LocalName == "preference" && (results.Attribute("name").Value == attrName)
+                         select results;
+
+            var itemnode = itemnodes.FirstOrDefault();
+            if (itemnode != null)
+            {
+                return itemnode.Attribute("value").Value;
             }
             return null;
         }
@@ -202,7 +213,11 @@
         {
             this.prepackedApps = new List<PreInstalPackageItem>();

-            foreach (XElement itemnode in pre_install_packages.Descendants("app_package"))
+            var itemnodes = from results in pre_install_packages.Descendants()
+                         where results.Name.LocalName == "app_package"
+                         select results;
+
+            foreach (XElement itemnode in itemnodes)
             {
                 string packageName = itemnode.Value;
                 String appId = itemnode.Attribute("id").Value;


Fix fatal failure of MSBuildLocator

Fix fatal failure of MSBuildLocator

--- source/Nuke.Common/ProjectModel/ProjectModelTasks.cs
+++ source/Nuke.Common/ProjectModel/ProjectModelTasks.cs
@@ -37,11 +37,21 @@
                 var _ = new ProjectCollection();
             }

-            MSBuildLocator.RegisterDefaults();
-            TriggerAssemblyResolution();
-            Environment.SetEnvironmentVariable("MSBuildExtensionsPath", msbuildExtensionPath);
-            Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildExePath);
-            Environment.SetEnvironmentVariable("MSBuildSDKsPath", msbuildSdkPath);
+            try
+            {
+                MSBuildLocator.RegisterDefaults();
+                TriggerAssemblyResolution();
+            }
+            catch (Exception exception)
+            {
+                Log.Warning("Could not register MSBuild: {Message}", exception.Message);
+            }
+            finally
+            {
+                Environment.SetEnvironmentVariable("MSBuildExtensionsPath", msbuildExtensionPath);
+                Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildExePath);
+                Environment.SetEnvironmentVariable("MSBuildSDKsPath", msbuildSdkPath);
+            }
         }

         public static Solution CreateSolution(string fileName = null, params Solution[] solutions)


code cleanup:  * use correct local member notation  * use s_ prefix for static members

--- PgSessionStateStoreProvider.cs
+++ PgSessionStateStoreProvider.cs
@@ -46,10 +46,10 @@
 {
 	public class PgSessionStateStoreProvider : SessionStateStoreProviderBase
 	{
-		private const string m_TableName = "Sessions";
-		private string m_ConnectionString = string.Empty;
-		private string m_ApplicationName = string.Empty;
-		private SessionStateSection m_Config = null;
+		private const string s_tableName = "Sessions";
+		private string m_connectionString = string.Empty;
+		private string m_applicationName = string.Empty;
+		private SessionStateSection m_config = null;

 		/// <summary>
 		/// System.Configuration.Provider.ProviderBase.Initialize Method
@@ -72,7 +72,7 @@
 			// Initialize the abstract base class.
 			base.Initialize(name, config);

-			m_ApplicationName = GetConfigValue(config["applicationName"], HostingEnvironment.ApplicationVirtualPath);
+			m_applicationName = GetConfigValue(config["applicationName"], HostingEnvironment.ApplicationVirtualPath);

 			// Get connection string.
 			string connStrName = config["connectionStringName"];
@@ -90,11 +90,11 @@
 					throw new ProviderException(Properties.Resources.ErrConnectionStringNullOrEmpty);
 				}

-				m_ConnectionString = ConnectionStringSettings.ConnectionString;
+				m_connectionString = ConnectionStringSettings.ConnectionString;
 			}

 			// Get <sessionState> configuration element.
-			m_Config = (SessionStateSection)WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath).GetSection("system.web/sessionState");
+			m_config = (SessionStateSection)WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath).GetSection("system.web/sessionState");
 		}

 		/// <summary>
@@ -133,14 +133,14 @@
 		/// </summary>
 		public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
 		{
-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 				{
-					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "INSERT INTO \"{0}\" (\"SessionId\", \"ApplicationName\", \"Created\", \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\") Values (@SessionId, @ApplicationName, @Created, @Expires, @Timeout, @Locked, @LockId, @LockDate, @Data, @Flags)", m_TableName);
+					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "INSERT INTO \"{0}\" (\"SessionId\", \"ApplicationName\", \"Created\", \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\") Values (@SessionId, @ApplicationName, @Created, @Expires, @Timeout, @Locked, @LockId, @LockDate, @Data, @Flags)", s_tableName);

 					dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
 					dbCommand.Parameters.Add("@Created", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
 					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes((Double)timeout);
 					dbCommand.Parameters.Add("@Timeout", NpgsqlDbType.Integer).Value = timeout;
@@ -219,16 +219,16 @@
 		/// </summary>
 		public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
 		{
-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 				{
-					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires, \"Locked\" = @Locked WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", m_TableName);
-
-					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes(m_Config.Timeout.Minutes);
+					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires, \"Locked\" = @Locked WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", s_tableName);
+
+					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes(m_config.Timeout.Minutes);
 					dbCommand.Parameters.Add("@Locked", NpgsqlDbType.Boolean).Value = false;
 					dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
 					dbCommand.Parameters.Add("@LockId", NpgsqlDbType.Integer).Value = lockId;

 					NpgsqlTransaction dbTrans = null;
@@ -284,14 +284,14 @@
 		/// </summary>
 		public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
 		{
-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 				{
-					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", m_TableName);
+					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", s_tableName);

 					dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
 					dbCommand.Parameters.Add("@LockId", NpgsqlDbType.Integer).Value = lockId;

 					NpgsqlTransaction dbTrans = null;
@@ -347,15 +347,15 @@
 		/// </summary>
 		public override void ResetItemTimeout(HttpContext context, string id)
 		{
-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 				{
-					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", m_TableName);
-
-					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes(m_Config.Timeout.Minutes);
+					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", s_tableName);
+
+					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes(m_config.Timeout.Minutes);
 					dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;

 					NpgsqlTransaction dbTrans = null;

@@ -413,7 +413,7 @@
 			// Serialize the SessionStateItemCollection as a string
 			string serializedItems = Serialize((SessionStateItemCollection)item.Items);

-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				using (NpgsqlCommand dbCommand = dbConn.CreateCommand(),
 						delCommand = dbConn.CreateCommand())
@@ -421,16 +421,16 @@
 					if (newItem)
 					{
 						// Delete existing expired session if exist
-						delCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", m_TableName);
+						delCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", s_tableName);

 						delCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-						delCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+						delCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;

 						// Insert new session data
-						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "INSERT INTO \"{0}\" (\"SessionId\", \"ApplicationName\", \"Created\", \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\") Values (@SessionId, @ApplicationName, @Created, @Expires, @Timeout, @Locked, @LockId, @LockDate, @Data, @Flags)", m_TableName);
+						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "INSERT INTO \"{0}\" (\"SessionId\", \"ApplicationName\", \"Created\", \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\") Values (@SessionId, @ApplicationName, @Created, @Expires, @Timeout, @Locked, @LockId, @LockDate, @Data, @Flags)", s_tableName);

 						dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
 						dbCommand.Parameters.Add("@Created", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
 						dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes((Double)item.Timeout);
 						dbCommand.Parameters.Add("@Timeout", NpgsqlDbType.Integer).Value = item.Timeout;
@@ -443,13 +443,13 @@
 					else
 					{
 						// Update existing session
-						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires, \"Locked\" = @Locked, \"Data\" = @Data WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", m_TableName);
+						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Expires\" = @Expires, \"Locked\" = @Locked, \"Data\" = @Data WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName AND \"LockId\" = @LockId", s_tableName);

 						dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now.AddMinutes((Double)item.Timeout);
 						dbCommand.Parameters.Add("@Locked", NpgsqlDbType.Boolean).Value = false;
 						dbCommand.Parameters.Add("@Data", NpgsqlDbType.Text).Value = serializedItems;
 						dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
 						dbCommand.Parameters.Add("@LockId", NpgsqlDbType.Integer).Value = lockId;
 					}

@@ -532,7 +532,7 @@
 			int timeout = 0;
 			string serializedItems = null;

-			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_ConnectionString))
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
 				NpgsqlTransaction dbTrans = null;
 				try
@@ -543,10 +543,10 @@
 					// Retrieve the current session item information and lock row
 					using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 					{
-						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "SELECT \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\" FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName FOR UPDATE", m_TableName);
+						dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "SELECT \"Expires\", \"Timeout\", \"Locked\", \"LockId\", \"LockDate\", \"Data\", \"Flags\" FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName FOR UPDATE", s_tableName);

 						dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+						dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;

 						using (NpgsqlDataReader reader = dbCommand.ExecuteReader(System.Data.CommandBehavior.SingleRow))
 						{
@@ -573,9 +573,9 @@

 					// If the actionFlags parameter is not InitializeItem, deserialize the stored SessionStateItemCollection
 					if (actionFlags == SessionStateActions.InitializeItem)
-						result = CreateNewStoreData(context, m_Config.Timeout.Minutes);
+						result = CreateNewStoreData(context, m_config.Timeout.Minutes);
 					else
-						result = new SessionStateStoreData(Deserialize(serializedItems), SessionStateUtility.GetSessionStaticObjects(context), m_Config.Timeout.Minutes);
+						result = new SessionStateStoreData(Deserialize(serializedItems), SessionStateUtility.GetSessionStaticObjects(context), m_config.Timeout.Minutes);

 					if (lockRecord)
 					{
@@ -583,14 +583,14 @@
 						// Obtain a lock to the record
 						using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
 						{
-							dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Locked\" = @Locked, \"LockId\" = @LockId,\"LockDate\" = @LockDate, \"Flags\" = @Flags WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", m_TableName);
+							dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "UPDATE \"{0}\" SET \"Locked\" = @Locked, \"LockId\" = @LockId,\"LockDate\" = @LockDate, \"Flags\" = @Flags WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", s_tableName);

 							dbCommand.Parameters.Add("@Locked", NpgsqlDbType.Boolean).Value = true;
 							dbCommand.Parameters.Add("@LockId", NpgsqlDbType.Integer).Value = lockId;
 							dbCommand.Parameters.Add("@LockDate", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
 							dbCommand.Parameters.Add("@Flags", NpgsqlDbType.Integer).Value = 0;
 							dbCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80).Value = id;
-							dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_ApplicationName;
+							dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;

 							dbCommand.ExecuteNonQuery();
 						}


fix ticket #3, implement support for Session_End event, based on a patch by C. Akkermans

--- PgSessionStateStoreProvider.cs
+++ PgSessionStateStoreProvider.cs
@@ -5,6 +5,7 @@
 //
 // Author:
 //	Daniel Nauck		<d.nauck(at)nauck-it.de>
+//	C. Akkermans		<>
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -51,6 +52,8 @@
 		private string m_connectionString = string.Empty;
 		private string m_applicationName = string.Empty;
 		private SessionStateSection m_config = null;
+		private bool m_enableExpireCallback = false;
+		private SessionStateItemExpireCallback m_expireCallback = null;

 		/// <summary>
 		/// System.Configuration.Provider.ProviderBase.Initialize Method
@@ -81,12 +84,16 @@
 			// Get <sessionState> configuration element.
 			m_config = (SessionStateSection)WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath).GetSection("system.web/sessionState");

-			// Setup expired session deletion timer
+			// Should automatic session garbage collection be turned on?
 			bool enableExpiredSessionAutoDeletion = Convert.ToBoolean(PgMembershipProvider.GetConfigValue(config["enableExpiredSessionAutoDeletion"], "false"), CultureInfo.InvariantCulture);
-			double expiredSessionAutoDeletionInterval = Convert.ToDouble(PgMembershipProvider.GetConfigValue(config["expiredSessionAutoDeletionInterval"], "1800000"), CultureInfo.InvariantCulture); //default: 30 minutes
-
+
 			if (!enableExpiredSessionAutoDeletion)
 				return;
+
+			m_enableExpireCallback = Convert.ToBoolean(PgMembershipProvider.GetConfigValue(config["enableSessionExpireCallback"], "false"), CultureInfo.InvariantCulture);
+
+			// Load session garbage collection configuration and setup garbage collection interval timer
+			double expiredSessionAutoDeletionInterval = Convert.ToDouble(PgMembershipProvider.GetConfigValue(config["expiredSessionAutoDeletionInterval"], "1800000"), CultureInfo.InvariantCulture); //default: 30 minutes

 			m_expiredSessionDeletionTimer = new System.Timers.Timer(expiredSessionAutoDeletionInterval);
 			m_expiredSessionDeletionTimer.Elapsed += new System.Timers.ElapsedEventHandler(ExpiredSessionDeletionTimer_Elapsed);
@@ -508,8 +515,15 @@
 		/// </summary>
 		public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
 		{
-			return false;
-		}
+			// Accept and store callback if session expire callback is enabled. If not, return false in order to inform SessionStateModule
+			// the session expire callback is not supported.
+			if (!m_enableExpireCallback)
+				return false;
+
+			m_expireCallback = expireCallback;
+			return true;
+		}
+
 		#endregion

 		#region private methods
@@ -675,27 +689,107 @@
 			return sessionItems;
 		}

+		/// <summary>
+		/// The ExpiredSessionDeletionTimer_Elapsed performs automatic session garbage collection by removing expired sessions from
+		/// the database.
+		/// </summary>
+		/// <param name="source"></param>
+		/// <param name="e"></param>
 		private void ExpiredSessionDeletionTimer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
 		{
+			/*
+			 * Determine mode of session garbage collection. If the session expire callback is disabled
+			 * one may simple delete all expired session from the session table. If however the session expire callback
+			 * is enabled, we need to load the session data for every expired session and invoke the expire callback
+			 * for each of these sessions prior to deletion.
+			 * Also check if an expire call back was actually defined. If m_expireCallback is null we also don't have to take
+			 * the more expensive path where every session is enumerated while there's no real need to do so.
+			 */
+
+			if (m_enableExpireCallback && m_expireCallback != null)
+				InvokeExpireCallbackAndDeleteSession();
+
+			else
+				DeleteExpiredSessionsFromDatabase();
+		}
+
+		/// <summary>
+		/// Load the session data for every expired session and invoke the expire callback
+		/// for each of these sessions prior to deletion.
+		/// </summary>
+		private void InvokeExpireCallbackAndDeleteSession()
+		{
+			Dictionary<string, SessionStateStoreData> expiredSessions = null;
+
+			// Start out by enumerating all expired sessions
 			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
 			{
-				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
-				{
-					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"Expires\" < @Expires AND \"ApplicationName\" = @ApplicationName", s_tableName);
-
-					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
-					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
+				using (NpgsqlCommand selectCommand = dbConn.CreateCommand())
+				{
+					selectCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "SELECT \"SessionId\", \"Data\" FROM \"{0}\" WHERE \"Expires\" < @Expires AND \"ApplicationName\" = @ApplicationName", s_tableName);
+
+					selectCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
+					selectCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
+
+					try
+					{
+						dbConn.Open();
+						selectCommand.Prepare();
+
+						using (NpgsqlDataReader reader = selectCommand.ExecuteReader())
+						{
+							if (!reader.HasRows)
+								return;
+
+							expiredSessions = new Dictionary<string, SessionStateStoreData>(reader.RecordsAffected);
+
+							// Get session data from data reader and reconstruct session.
+							// NOTE:	I'm not sure if I should pass any static objects to the constructor of the SessionStateStoreData class.
+							//			Seems to me you should not since garbage collection is say highly unlikely to be run in an actual http context.
+							while (reader.Read())
+							{
+								string sessionId = reader.GetString(0);
+								string serializedItems = reader.IsDBNull(1) ? null : reader.GetString(1);
+
+								expiredSessions.Add(sessionId, new SessionStateStoreData(Deserialize(serializedItems), new HttpStaticObjectsCollection(), Convert.ToInt32(m_config.Timeout.TotalMinutes)));
+							}
+						}
+					}
+					catch (Exception ex)
+					{
+						Trace.WriteLine(ex.ToString());
+
+						if (dbConn != null)
+							dbConn.Close();
+
+						throw new ProviderException(Properties.Resources.ErrOperationAborted);
+					}
+				}
+
+				using (NpgsqlCommand deleteCommand = dbConn.CreateCommand())
+				{
+					deleteCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"SessionId\" = @SessionId AND \"ApplicationName\" = @ApplicationName", s_tableName);
+
+					deleteCommand.Parameters.Add("@SessionId", NpgsqlDbType.Varchar, 80);
+					deleteCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;

 					NpgsqlTransaction dbTrans = null;
-
+
 					try
 					{
-						dbConn.Open();
-						dbCommand.Prepare();
-
+						deleteCommand.Prepare();
 						dbTrans = dbConn.BeginTransaction();

-						dbCommand.ExecuteNonQuery();
+						// Actually invoke session expire callback and delete session from the session table.
+						foreach (KeyValuePair<string, SessionStateStoreData> expiredSession in expiredSessions)
+						{
+							// TODO: use async invocation insted?
+							m_expireCallback.Invoke(expiredSession.Key, expiredSession.Value);
+
+							deleteCommand.Parameters["@SessionId"].Value = expiredSession.Key;
+
+							deleteCommand.ExecuteNonQuery();
+						}

 						// Attempt to commit the transaction
 						dbTrans.Commit();
@@ -733,6 +827,69 @@
 				}
 			}
 		}
+
+		/// <summary>
+		/// Delete all expired session from the session table.
+		/// </summary>
+		private void DeleteExpiredSessionsFromDatabase()
+		{
+			using (NpgsqlConnection dbConn = new NpgsqlConnection(m_connectionString))
+			{
+				using (NpgsqlCommand dbCommand = dbConn.CreateCommand())
+				{
+					dbCommand.CommandText = string.Format(CultureInfo.InvariantCulture, "DELETE FROM \"{0}\" WHERE \"Expires\" < @Expires AND \"ApplicationName\" = @ApplicationName", s_tableName);
+
+					dbCommand.Parameters.Add("@Expires", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
+					dbCommand.Parameters.Add("@ApplicationName", NpgsqlDbType.Varchar, 255).Value = m_applicationName;
+
+					NpgsqlTransaction dbTrans = null;
+
+					try
+					{
+						dbConn.Open();
+						dbCommand.Prepare();
+
+						dbTrans = dbConn.BeginTransaction();
+
+						dbCommand.ExecuteNonQuery();
+
+						// Attempt to commit the transaction
+						dbTrans.Commit();
+					}
+					catch (Exception ex)
+					{
+						Trace.WriteLine(ex.ToString());
+
+						if (dbTrans != null)
+						{
+							try
+							{
+								// Attempt to roll back the transaction
+								Trace.WriteLine(Properties.Resources.LogRollbackAttempt);
+								dbTrans.Rollback();
+							}
+							catch (Exception re)
+							{
+								// Rollback failed
+								Trace.WriteLine(Properties.Resources.ErrRollbackFailed);
+								Trace.WriteLine(re.ToString());
+							}
+						}
+
+						throw new ProviderException(Properties.Resources.ErrOperationAborted);
+					}
+					finally
+					{
+						if (dbTrans != null)
+							dbTrans.Dispose();
+
+						if (dbConn != null)
+							dbConn.Close();
+					}
+				}
+			}
+		}
+
 		#endregion
 	}
 }


Added missing test to SymbolExtensionsTests

(I have to learn how to skip them in Fixie and how to test for exceptions using Shouldly)

--- tests/FixieSpec.Tests/SymbolExtensionsTests.cs
+++ tests/FixieSpec.Tests/SymbolExtensionsTests.cs
@@ -33,6 +33,14 @@
             methodInfo.IsStatic.ShouldBeTrue();
         }

+        public void ShoulfFailForInvalidExpressions()
+        {
+        }
+
+        public void ShoulfFailForNonMethodExpressions()
+        {
+        }
+
         class TestClass
         {
             public void AMethod()


Put MD5CryptoServiceProvider into using clause

--- source/FFImageLoading.Shared/Helpers/MD5Helper.cs
+++ source/FFImageLoading.Shared/Helpers/MD5Helper.cs
@@ -9,16 +9,20 @@
     {
         public string MD5(Stream stream)
         {
-            var hashProvider = new MD5CryptoServiceProvider();
-            var bytes = hashProvider.ComputeHash(stream);
-            return BitConverter.ToString(bytes)?.ToSanitizedKey();
+            using (var hashProvider = new MD5CryptoServiceProvider())
+            {
+                var bytes = hashProvider.ComputeHash(stream);
+                return BitConverter.ToString(bytes)?.ToSanitizedKey();
+            }
         }

         public string MD5(string input)
         {
-            var hashProvider = new MD5CryptoServiceProvider();
-            var bytes = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(input));
-            return BitConverter.ToString(bytes)?.ToSanitizedKey();
+            using (var hashProvider = new MD5CryptoServiceProvider())
+            {
+                var bytes = hashProvider.ComputeHash(Encoding.UTF8.GetBytes(input));
+                return BitConverter.ToString(bytes)?.ToSanitizedKey();
+            }
         }
     }
 }


Fix for possible database upgrade fail

Fix for possible database upgrade fail

--- source/Playnite/Database/GameDatabaseMigration.cs
+++ source/Playnite/Database/GameDatabaseMigration.cs
@@ -544,6 +544,11 @@
                 {
                     if (game.TryGetValue(origKey, out var storedObj))
                     {
+                        if (storedObj == null)
+                        {
+                            return;
+                        }
+
                         var gameObjs = new List<Guid>();
                         var oldLIst = (storedObj as JArray).ToObject<List<string>>();
                         foreach (var oldObj in oldLIst)


Ignore invalid items during datbase migration

--- source/Playnite/Database/GameDatabaseMigration.cs
+++ source/Playnite/Database/GameDatabaseMigration.cs
@@ -204,6 +204,14 @@
                                 if (oldItem == null)
                                 {
                                     logger.Warn($"Failed to load old database file {file}, it's empty.");
+                                    continue;
+                                }
+
+                                // No idea how these get created, most likely by some 3rd party extension.
+                                // They cause several issues so don't migrate them.
+                                if (oldItem.Name.IsNullOrWhiteSpace())
+                                {
+                                    logger.Warn($"Failed to load old database file {file}, has empty name.");
                                     continue;
                                 }


Small db migration fix

Small db migration fix

--- source/Playnite/Database/GameDatabaseMigration.cs
+++ source/Playnite/Database/GameDatabaseMigration.cs
@@ -642,23 +642,26 @@
             if (dbSettings.Version == 3 && NewFormatVersion > 3)
             {
                 var filesDir = Path.Combine(databasePath, filesDirName);
-                foreach (var dir in Directory.GetDirectories(filesDir))
-                {
-                    try
-                    {
-                        Directory.GetFiles(dir, "*.exe").ForEach(a =>
-                        {
-                            // Only delete files named as guid as those are 99% made by 2618 bug
-                            // People sometimes put foreign files into libary folder :|, so we don't want to delete something else.
-                            if (Guid.TryParse(Path.GetFileNameWithoutExtension(a), out var _))
-                            {
-                                File.Delete(a);
-                            }
-                        });
-                    }
-                    catch (Exception e)
-                    {
-                        logger.Error(e, "Failed to delete file.");
+                if (Directory.Exists(filesDir))
+                {
+                    foreach (var dir in Directory.GetDirectories(filesDir))
+                    {
+                        try
+                        {
+                            Directory.GetFiles(dir, "*.exe").ForEach(a =>
+                            {
+                                // Only delete files named as guid as those are 99% made by 2618 bug
+                                // People sometimes put foreign files into libary folder :|, so we don't want to delete something else.
+                                if (Guid.TryParse(Path.GetFileNameWithoutExtension(a), out var _))
+                                {
+                                    File.Delete(a);
+                                }
+                            });
+                        }
+                        catch (Exception e)
+                        {
+                            logger.Error(e, "Failed to delete file.");
+                        }
                     }
                 }


Fix: Crash when editing game metadata #403

--- source/PlayniteUI/ViewModels/GameEditViewModel.cs
+++ source/PlayniteUI/ViewModels/GameEditViewModel.cs
@@ -1129,7 +1129,7 @@
                         }
                     }
                 }
-                else
+                else if (File.Exists(EditingGame.Icon))
                 {
                     var iconPath = EditingGame.Icon;
                     var fileName = Guid.NewGuid().ToString() + Path.GetExtension(iconPath);
@@ -1189,7 +1189,7 @@
                         }
                     }
                 }
-                else
+                else if (File.Exists(EditingGame.Image))
                 {
                     var imagePath = EditingGame.Image;
                     var fileName = Guid.NewGuid().ToString() + Path.GetExtension(imagePath);
@@ -1275,7 +1275,7 @@
                             Game.BackgroundImage = EditingGame.BackgroundImage;
                         }
                     }
-                    else
+                    else if (File.Exists(EditingGame.BackgroundImage))
                     {
                         var imagePath = EditingGame.BackgroundImage;
                         var fileName = Guid.NewGuid().ToString() + Path.GetExtension(imagePath);


SV-924 Correct controller tests for NotFound and BadRequest

--- src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/TrainingProgrammeControllerTests/WhenGettingATrainingProgrammeVersion.cs
+++ src/CommitmentsV2/SFA.DAS.CommitmentsV2.Api.UnitTests/Controllers/TrainingProgrammeControllerTests/WhenGettingATrainingProgrammeVersion.cs
@@ -37,14 +37,14 @@
         }

         [Test, MoqAutoData]
-        public async Task And_ThereIsAnError_Then_ReturnBadRequest(
+        public async Task And_StandardNotFound_Then_ReturnNotFound(
             int courseCode,
             GetTrainingProgrammeVersionRequest request,
             [Frozen] Mock<IMediator> mediator,
             TrainingProgrammeController controller)
         {
             mediator.Setup(m => m.Send(It.Is<GetTrainingProgrammeVersionQuery>(q => q.CourseCode == courseCode && q.StartDate == request.StartDate), It.IsAny<CancellationToken>()))
-                .ReturnsAsync((GetTrainingProgrammeVersionQueryResult)null);
+                .ReturnsAsync(new GetTrainingProgrammeVersionQueryResult());

             var response = await controller.GetTrainingProgrammeVersion(courseCode, request) as NotFoundResult;


Login issue, user receives a temp profile

https://github.com/MutonUfoAI/pgina/issues/12

--- Plugins/pgSMB/pgSMB/Roaming.cs
+++ Plugins/pgSMB/pgSMB/Roaming.cs
@@ -134,6 +134,10 @@
                         userDel(settings, username, password);
                         return new BooleanResult() { Success = false, Message = string.Format("Unable to add user {0}", username) };
                     }
+                    if (!Roaming.CreateRoamingFolder(settings, username))
+                    {
+                        return new BooleanResult() { Success = false, Message = string.Format("Unable to create the Roaming folder {0}", settings["RoamingDest_real"]) };
+                    }
                 }
             }
             catch (Exception ex)


Update 批量合并同名文档：add CLI argument and null-conditional Operators

--- 单个文件的程序/批量合并同名文档.cs
+++ 单个文件的程序/批量合并同名文档.cs
@@ -9,10 +9,10 @@
 {
     class Program
     {
-        static void Main()
+        static void Main(string[] args)
         {
             Directory.GetFiles(
-                Environment.CurrentDirectory, "*.txt", SearchOption.AllDirectories
+                args?[1] ?? Environment.CurrentDirectory, "*.txt", SearchOption.AllDirectories
                 ).GroupBy(
                     x => Path.GetFileName(x)
                     ).AsParallel().ForAll(
@@ -22,7 +22,8 @@
                             foreach (var item in x)
                                 using (TextReader tr = new StreamReader(item))
                                 {
-                                    sb.AppendLine(tr.ReadToEnd() + Environment.NewLine);
+                                    sb.AppendLine(tr.ReadToEnd());
+                                    sb.AppendLine();
                                 }
                             using (TextWriter tw = new StreamWriter(x.Key))
                             {


Fix touch AF

Fix touch AF

--- Project/Pages/MainPage.xaml.cs
+++ Project/Pages/MainPage.xaml.cs
@@ -543,7 +543,7 @@
             cameraManager.ZoomInfoUpdated += ZoomInfoUpdated;
             cameraManager.ShowToast += ShowToast;
             ToastApparance.Completed += ToastApparance_Completed;
-            ScreenImage.ManipulationCompleted += ScreenImage_ManipulationCompleted;
+            FraimingGrids.ManipulationCompleted += FraimingGrids_ManipulationCompleted;

             CameraButtons.ShutterKeyPressed += CameraButtons_ShutterKeyPressed;
             CameraButtons.ShutterKeyHalfPressed += CameraButtons_ShutterKeyHalfPressed;
@@ -738,7 +738,7 @@
             cameraManager.RequestHalfPressShutter();
         }

-        void ScreenImage_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
+        void FraimingGrids_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
         {
             if (cpm.IsShowing())
             {
@@ -763,12 +763,12 @@
             {
                 return;
             }
-            var image = sender as Image;
+            var grids = sender as FramingGrids;
             var touchX = e.ManipulationOrigin.X;
             var touchY = e.ManipulationOrigin.Y;

-            var posX = touchX * 100.0 / image.ActualWidth;
-            var posY = touchY * 100.0 / image.ActualHeight;
+            var posX = touchX * 100.0 / grids.ActualWidth;
+            var posY = touchY * 100.0 / grids.ActualHeight;

             Dispatcher.BeginInvoke(() =>
             {
@@ -811,7 +811,7 @@

             cameraManager.OnAfStatusChanged -= cameraManager_OnAfStatusChanged;

-            ScreenImage.ManipulationCompleted -= ScreenImage_ManipulationCompleted;
+            FraimingGrids.ManipulationCompleted -= FraimingGrids_ManipulationCompleted;
             cameraManager.IntervalManager.Stop();

             if (svd != null)


fixed auth loop bug

fixed auth loop bug

--- Agencies/Agencies.iOS/View/RootTabBarController.cs
+++ Agencies/Agencies.iOS/View/RootTabBarController.cs
@@ -72,16 +72,16 @@
         {
             Task.Run (async () =>
             {
-                //BotClient.Shared.ResetCurrentUser ();
-                //ClientAuthManager.Shared.LogoutAuthProviders ();
-                //throw new Exception ("stop and re-comment out lines");
+               //BotClient.Shared.ResetCurrentUser();
+               //ClientAuthManager.Shared.LogoutAuthProviders();
+               //throw new Exception("stop and re-comment out lines");

-                try
+               try
                 {
                     var details = ClientAuthManager.Shared.ClientAuthDetails;

-                    // try authenticating with an existing token
-                    if (AgenciesClient.Shared.AuthUser == null && details != null)
+                   // try authenticating with an existing token
+                   if (AgenciesClient.Shared.AuthUser == null && details != null)
                     {
                         var user = await AgenciesClient.Shared.GetAuthUserConfigAsync () ?? await AgenciesClient.Shared.GetAuthUserConfigAsync (details?.Token, details?.AuthCode);

@@ -103,8 +103,11 @@
                         }
                     }
                     else // otherwise prompt the user to login
-                    {
-                        BeginInvokeOnMainThread (() => presentAuthController ());
+                   {
+                        if (AgenciesClient.Shared.AuthUser == null)
+                        {
+                            BeginInvokeOnMainThread (() => presentAuthController ());
+                        }
                     }
                 }
                 catch (Exception ex)


Update LiquidETFUniverse.cs

Update LiquidETFUniverse.cs


fix unit test

fix unit test

--- src/BinaryKits.ZPLUtility.UnitTest/BarcodeTest.cs
+++ src/BinaryKits.ZPLUtility.UnitTest/BarcodeTest.cs
@@ -10,11 +10,11 @@
     public class BarcodeTest
     {
         [TestMethod]
-        public void BarCode39()
+        public void Barcode39()
         {
             var elements = new List<ZPLElementBase>
             {
-                new ZPLBarCode39("123ABC", 100, 100)
+                new ZPLBarcode39("123ABC", 100, 100)
             };

             var renderEngine = new ZPLEngine(elements);
@@ -26,11 +26,11 @@
         }

         [TestMethod]
-        public void BarCode128()
+        public void Barcode128()
         {
             var elements = new List<ZPLElementBase>
             {
-                new ZPLBarCode128("123ABC", 100, 300)
+                new ZPLBarcode128("123ABC", 100, 300)
             };

             var renderEngine = new ZPLEngine(elements);


BL-4824 Exception in Login dialog

BL-4824 Exception in Login dialog

* toast instead of throwing exception

--- src/BloomExe/WebLibraryIntegration/LoginDialog.cs
+++ src/BloomExe/WebLibraryIntegration/LoginDialog.cs
@@ -4,11 +4,9 @@
 using System.Text.RegularExpressions;
 using System.Windows.Forms;
 using Bloom.Properties;
-using Bloom.Publish;
 using Bloom.Publish.BloomLibrary;
 using L10NSharp;
 using SIL.Code;
-using SIL.Reporting;

 namespace Bloom.WebLibraryIntegration
 {
@@ -58,7 +56,7 @@
 			catch (Exception)
 			{
 				MessageBox.Show(this, LocalizationManager.GetString("PublishTab.Upload.Login.LoginConnectFailed", "Bloom could not connect to the server to verify your login. Please check your network connection."),
-					LocalizationManager.GetString("PublishTab.Upload.Login.LoginFailed", "Login failed"),
+					LoginFailureString,
 					MessageBoxButtons.OK,
 					MessageBoxIcon.Error);
 				return;
@@ -71,13 +69,21 @@
 			else
 			{
 				MessageBox.Show(this, LocalizationManager.GetString("PublishTab.Upload.Login.PasswordMismatch", "Password and user ID did not match"),
-					LocalizationManager.GetString("PublishTab.Upload.Login.LoginFailed", "Login failed"),
+					LoginFailureString,
 					MessageBoxButtons.OK,
 					MessageBoxIcon.Error);
 			}
 		}

-		private string LoginOrSignupConnectionFailedString
+
+		private static string LoginFailureString
+		{
+			get
+			{
+				return LocalizationManager.GetString("PublishTab.Upload.Login.LoginFailed", "Login failed");
+			}
+		}
+		private static string LoginOrSignupConnectionFailedString
 		{
 			get
 			{
@@ -104,7 +110,7 @@
 			}
 			catch (Exception e)
 			{
-				ErrorReport.NotifyUserOfProblem(e, LoginOrSignupConnectionFailedString);
+				NonFatalProblem.Report(ModalIf.None, PassiveIf.All, LoginFailureString, LoginOrSignupConnectionFailedString, e);
 				return;
 			}
 			if (userExists)
@@ -129,7 +135,7 @@
 			}
 			catch (Exception e)
 			{
-				ErrorReport.NotifyUserOfProblem(e, LoginOrSignupConnectionFailedString);
+				NonFatalProblem.Report(ModalIf.None, PassiveIf.All, LoginFailureString, LoginOrSignupConnectionFailedString, e);
 			}
 		}


Enable out-of-proc tests on .NET Core

Enable out-of-proc tests on .NET Core

Partial fix for #933

--- src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
+++ src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
@@ -269,9 +269,7 @@
         /// <summary>
         /// A simple successful build, out of process only.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -284,9 +282,7 @@
         /// <summary>
         /// A simple successful build, out of process only. Triggered by setting build parameters' DisableInProcNode to true.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -464,11 +460,7 @@
         /// Make sure when we launch a child node and set MsBuildForwardAllPropertiesFromChild that we get all of our properties. This needs to happen
         /// even if the msbuildforwardpropertiesfromchild is set to something.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#else
-        [Fact]
-#endif
+        [Fact]
         public void MsBuildForwardAllPropertiesFromChildLaunchChildNode()
         {
             string contents = ObjectModelHelpers.CleanupFileContents(@"
@@ -686,9 +678,7 @@
         /// Make sure when if the environment variable MsBuildForwardPropertiesFromChild is set to empty and
         /// we launch a child node that we get no properties
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -823,9 +813,7 @@
         /// When a child node is launched by default we should not send any properties.
         /// we launch a child node that we get no properties
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2057,9 +2045,7 @@
         /// <summary>
         /// Verify that disabling the in-proc node doesn't cause projects which don't require it to fail.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2101,11 +2087,7 @@
         /// <summary>
         /// Verify that disabling the in-proc node when a project requires it will cause the build to fail, but not crash.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#else
-        [Fact]
-#endif
+        [Fact]
         public void Regress239661_NodeUnavailable()
         {
             string contents = ObjectModelHelpers.CleanupFileContents(@"
@@ -2134,9 +2116,7 @@
         /// <summary>
         /// Ensures that properties and items are transferred to the out-of-proc node when an instance is used to start the build.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2211,9 +2191,7 @@
         /// <summary>
         /// Ensures that a limited set of properties are transferred from a project instance to an OOP node.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2731,9 +2709,7 @@
         /// second request will bail out where the first request did, as though it had
         /// executed the target, rather than skipping and continuing.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2842,9 +2818,7 @@
         /// expected in the first request, but be skipped by the second (since if it's "skipping
         /// unsuccessful", it can assume that all other OnError targets have also already been run)
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -2983,9 +2957,7 @@
         /// they're marked as ContinueOnError=ErrorAndContinue, then we won't bail, but
         /// will continue executing (on the first request) or skipping (on the second)
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -3096,9 +3068,7 @@
         /// This test verifies that if the errors are in AfterTargets, we still
         /// exit as though the target that those targets run after has already run.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
 #else
         [Fact]
@@ -3459,9 +3429,7 @@
         /// submissions aren't restricted to running strictly serially by the single in-proc
         /// node.
         /// </summary>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#elif MONO
+#if MONO
         [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1245")]
 #else
         [Fact]
@@ -3565,12 +3533,8 @@
         /// This differs from transferring a project instance to an out-of-proc node because in this case the project
         /// was loaded by MSBuild, not supplied directly by the user.
         /// </remarks>
-#if RUNTIME_TYPE_NETCORE
-        [Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
-#else
         [Fact]
         [Trait("Category", "mono-osx-failing")]
-#endif
         public void Regress265010()
         {
             string contents = ObjectModelHelpers.CleanupFileContents(@"


Dap: nitpick to fix coding style

Dap: nitpick to fix coding style

--- src/Dap/Banshee.Dap/Banshee.Dap/DapLibrarySync.cs
+++ src/Dap/Banshee.Dap/Banshee.Dap/DapLibrarySync.cs
@@ -253,8 +253,8 @@
             }
         }

-        internal class PossibleUserErrorException : ApplicationException {
-
+        internal class PossibleUserErrorException : ApplicationException
+        {
             internal int TracksToRemove { get; private set; }

             public PossibleUserErrorException (int tracksToRemove) : base ()


Minor documentation updates

Minor documentation updates

--- src/DatenMeister.XMI/UmlBootstrap/Bootstrapper.cs
+++ src/DatenMeister.XMI/UmlBootstrap/Bootstrapper.cs
@@ -126,7 +126,8 @@
         /// <summary>
         /// Performs a full bootstrap by reading in the uml class
         /// </summary>
-        /// <param name="path">Path to be used</param>
+        /// <param name="pathUml">Path of XMI file containing the UML file to be used</param>
+        /// <param name="pathMof">Path of XMI file containing the MOF specification being</param>
         /// <returns>The instance of the bootstrapper</returns>
         public static Bootstrapper PerformFullBootstrap(string pathUml, string pathMof)
         {


TimeSpan.Milliseconds does not return the number of milliseconds in the timespan, but TotalMilliseconds does.

--- src/app/SharpRaven/RavenClient.cs
+++ src/app/SharpRaven/RavenClient.cs
@@ -181,8 +181,8 @@
             try
             {
                 var request = (HttpWebRequest) WebRequest.Create(dsn.SentryUri);
-                request.Timeout = Timeout.Milliseconds;
-                request.ReadWriteTimeout = Timeout.Milliseconds;
+                request.Timeout = (int)Timeout.TotalMilliseconds;
+                request.ReadWriteTimeout = (int)Timeout.TotalMilliseconds;
                 request.Method = "POST";
                 request.Accept = "application/json";
                 request.Headers.Add("X-Sentry-Auth", PacketBuilder.CreateAuthenticationHeader(dsn));


QuerySemantics.Cast: No Fallback.

QuerySemantics.Cast: No Fallback.

--- src/ExRam.Gremlinq.Core/Queries/QuerySemantics.cs
+++ src/ExRam.Gremlinq.Core/Queries/QuerySemantics.cs
@@ -102,12 +102,9 @@
                         .GetGenericTypeDefinition()
                         .MakeGenericType(arguments);
                 }
-
-                if (typeof(TResult).IsAssignableFrom(arguments[0]))
-                    return this;
             }

-            return fallback;
+            return this;
         }

         internal bool IsVertex


Open repository after cloning

Open repository after cloning

--- src/GitHub.App/Services/RepositoryCloneService.cs
+++ src/GitHub.App/Services/RepositoryCloneService.cs
@@ -118,10 +118,8 @@
             {
                 await CloneRepository(cloneUrl, repositoryPath, progress);
             }
-            else
-            {
-                teamExplorerServices.OpenRepository(repositoryPath);
-            }
+
+            teamExplorerServices.OpenRepository(repositoryPath);
         }

         /// <inheritdoc/>


Use ETag for conditional requests. Fixes #2.

The URIs and ETags of all GitHub API responses that are used to construct the feed are hashed together to make the ETag for the feed itself.

This doesn't save any processing time on the server, but benefits the client by allowing it to easily reuse its own cached data.

--- src/GitHubFeeds/Controllers/CommentsController.cs
+++ src/GitHubFeeds/Controllers/CommentsController.cs
@@ -5,6 +5,7 @@
 using System.IO;
 using System.Linq;
 using System.Net;
+using System.Security.Cryptography;
 using System.ServiceModel.Syndication;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -23,14 +24,15 @@
 		{
 			AsyncManager.OutstandingOperations.Increment();

+			ListData data = new ListData { RequestETag = Request.Headers["If-None-Match"] };
 			Uri uri = CreateUri(p, 1, 1);
 			HttpWebRequest request = CreateRequest(p, uri);
 			request.GetHttpResponseAsync()
-				.ContinueWith(t => GetCommentCount(p, t))
+				.ContinueWith(t => GetCommentCount(t))
 				.ContinueWith(t => GetCommentPages(p, t.Result))
-				.ContinueWith(t => Task.Factory.ContinueWhenAll(t.Result, ts => GetComments(p, ts))).Unwrap()
+				.ContinueWith(t => Task.Factory.ContinueWhenAll(t.Result, ts => GetComments(data, ts))).Unwrap()
 				.ContinueWith(t => CreateFeed(p, t.Result))
-				.ContinueWith(t => CreateResult(p, t))
+				.ContinueWith(t => CreateResult(data, t))
 				.ContinueWith(t =>
 				{
 					AsyncManager.Parameters["result"] = t.Result;
@@ -66,7 +68,7 @@
 		}

 		// Gets the number of comments for a repo.
-		private static int GetCommentCount(ListParameters p, Task<HttpWebResponse> responseTask)
+		private static int GetCommentCount(Task<HttpWebResponse> responseTask)
 		{
 			string linkHeader;
 			using (HttpWebResponse response = responseTask.Result)
@@ -120,8 +122,11 @@
 		}

 		// Merges the comments returned from multiple HTTP requests and returns the last 50.
-		private static List<GitHubComment> GetComments(ListParameters p, Task<HttpWebResponse>[] tasks)
-		{
+		private static List<GitHubComment> GetComments(ListData data, Task<HttpWebResponse>[] tasks)
+		{
+			// concatenate all the response URIs and ETags; we will use this to build our own ETag
+			var responseETags = new List<string>();
+
 			// download comments as JSON and deserialize them
 			List<GitHubComment> comments = new List<GitHubComment>();
 			foreach (Task<HttpWebResponse> task in tasks)
@@ -130,6 +135,11 @@
 				{
 					if (response.StatusCode != HttpStatusCode.OK)
 						throw new ApplicationException("GitHub server returned " + response.StatusCode);
+
+					// if the response has an ETag, add it to the list of all ETags
+					string eTag = response.Headers[HttpResponseHeader.ETag];
+					if (!string.IsNullOrEmpty(eTag))
+						responseETags.Add(response.ResponseUri.AbsoluteUri + ":" + eTag);

 					// TODO: Use asynchronous reads on this asynchronous stream
 					// TODO: Read encoding from Content-Type header; don't assume UTF-8
@@ -137,6 +147,21 @@
 					using (TextReader reader = new StreamReader(stream, Encoding.UTF8))
 						comments.AddRange(JsonSerializer.DeserializeFromReader<List<GitHubComment>>(reader));
 				}
+			}
+
+			// if each response had an ETag, build our own ETag from that data
+			if (responseETags.Count == tasks.Length)
+			{
+				// concatenate all the ETag data
+				string eTagData = responseETags.Join("\n");
+
+				// hash it
+				byte[] md5;
+				using (MD5 hash = MD5.Create())
+					md5 = hash.ComputeHash(Encoding.UTF8.GetBytes(eTagData));
+
+				// the ETag is the quoted MD5 hash
+				data.ResponseETag = "\"" + string.Join("", md5.Select(by => by.ToString("x2", CultureInfo.InvariantCulture))) + "\"";
 			}

 			return comments
@@ -187,19 +212,25 @@
 				HttpUtility.HtmlEncode(comment.commit_id.Substring(0, 8)), comment.body_html);
 		}

-		private ActionResult CreateResult(ListParameters p, Task<SyndicationFeed> feedTask)
+		private ActionResult CreateResult(ListData data, Task<SyndicationFeed> feedTask)
 		{
 			if (feedTask.IsFaulted)
 				return new HttpStatusCodeResult((int) HttpStatusCode.InternalServerError, feedTask.Exception.Message);

-			SyndicationFeed feed = feedTask.Result;
-			Response.AppendHeader("Last-Modified", feed.LastUpdatedTime.ToString("r"));
-
-			DateTime ifModifiedSince;
-			if (DateTime.TryParse(Request.Headers["If-Modified-Since"], out ifModifiedSince) && ifModifiedSince >= feed.LastUpdatedTime)
+			if (data.ResponseETag != null)
+				Response.AppendHeader("ETag", data.ResponseETag);
+
+			if (data.RequestETag == data.ResponseETag)
 				return new HttpStatusCodeResult((int) HttpStatusCode.NotModified);

 			return new SyndicationFeedAtomResult(feedTask.Result);
+		}
+
+		// ListData contains private state that needs to be communicated between the various async methods invoked by List.
+		private sealed class ListData
+		{
+			public string RequestETag { get; set; }
+			public string ResponseETag { get; set; }
 		}
 	}
 }


Reformat

Reformat

Former-commit-id: 07236d8ba33f8ac2d1e4107e8aaa825dc21b75c4

--- tool_src/TileCutter/Properties/AssemblyInfo.cs
+++ tool_src/TileCutter/Properties/AssemblyInfo.cs
@@ -12,8 +12,8 @@
 // For more information, please refer to <http://unlicense.org>

 using System.Reflection;
+using System.Resources;
 using System.Runtime.InteropServices;
-using System.Resources;

 // General Information about an assembly is controlled through the following
 // set of attributes. Change these attribute values to modify the information
@@ -51,4 +51,4 @@

 [assembly: AssemblyVersion("1.4.0.0")]
 [assembly: AssemblyFileVersion("1.4.0.0")]
-[assembly: NeutralResourcesLanguageAttribute("en")]
+[assembly: NeutralResourcesLanguage("en")]


Fixed bug with nested coroutines that ended instantly resulting in the parent coroutine waiting indefinitely.

--- Archon.SwissArmyLib/Coroutines/BetterCoroutines.cs
+++ Archon.SwissArmyLib/Coroutines/BetterCoroutines.cs
@@ -176,8 +176,10 @@
             if (!continueRunning)
             {
                 IdToCoroutine.Remove(coroutine.Id);
+                list.Remove(coroutine);
+                if (coroutine.Parent != null)
+                    coroutine.Parent.Child = null;
                 PoolHelper<BetterCoroutine>.Despawn(coroutine);
-                list.Remove(coroutine);
             }

             _current = prevCurrent;


Master.Readメソッドを最適化

Master.Readメソッドを最適化

--- ux/Master.cs
+++ ux/Master.cs
@@ -126,6 +126,7 @@
             this.handleQueue = new Queue<Handle>();

             this.Reset();
+            this.PrepareCompressor();
         }
         #endregion

@@ -220,14 +221,16 @@
             // Part.Generate にはサンプル数を与える
             for (int k = 0; k < this.partCount; k++)
             {
-                if (!this.parts[k].IsSounding)
-                    continue;
-
-                this.parts[k].Generate(count / 2);
+                Part part = this.parts[k];
+
+                if (part.IsSounding)
+                {
+                    part.Generate(count / 2);

                 // 波形合成
                 for (int i = offset, j = 0; j < count; i++, j++)
-                    buffer[i] += this.parts[k].Buffer[j];
+                        buffer[i] += part.Buffer[j];
+                }
             }

             for (int i = offset, length = offset + count; i < length; i++)
@@ -235,15 +238,20 @@
                 float output = buffer[i] * this.masterVolume * this.gain;

                 if (output == 0.0f)
-                    continue;
-
+                {
+                    buffer[i] = 0.0f;
+                }
+                else
+                {
                 // 圧縮
-                output =
-                    ((output > this.compressorThreshold) ? this.upover + this.compressorRatio * output :
-                    (output < -this.compressorThreshold) ? this.downover + this.compressorRatio * output : output);
+                    if (output > this.compressorThreshold)
+                        output = this.upover + this.compressorRatio * output;
+                    else if (output < -this.compressorThreshold)
+                        output = this.downover + this.compressorRatio * output;

                 // クリッピングと代入
                 buffer[i] = (output > 1.0f) ? 1.0f : (output < -1.0f) ? -1.0f : output;
+            }
             }

             return count;


Renamed inconsistently named multiple NodeById methods

--- umbraco.MacroEngines.Juno/RazorDynamicNode/RazorLibraryCore.cs
+++ umbraco.MacroEngines.Juno/RazorDynamicNode/RazorLibraryCore.cs
@@ -32,24 +32,25 @@
         {
             return new DynamicNode(Id);
         }
-        public DynamicNodeList NodeById(List<object> Ids)
+        public DynamicNodeList NodesById(List<object> Ids)
         {
             List<DynamicNode> nodes = new List<DynamicNode>();
             foreach (object eachId in Ids)
                 nodes.Add(new DynamicNode(eachId));
             return new DynamicNodeList(nodes);
         }
-        public DynamicNodeList NodeById(List<int> Ids)
+        public DynamicNodeList NodesById(List<int> Ids)
         {
             List<DynamicNode> nodes = new List<DynamicNode>();
             foreach (int eachId in Ids)
                 nodes.Add(new DynamicNode(eachId));
             return new DynamicNodeList(nodes);
         }
-        public DynamicNodeList NodeById(params object[] Ids)
+        public DynamicNodeList NodesById(params object[] Ids)
         {
             return NodeById(Ids.ToList());
         }
+
         public DynamicNode MediaById(int Id)
         {
             return new DynamicNode(new DynamicBackingItem(ExamineBackedMedia.GetUmbracoMedia(Id)));


Finished Authorize API

Finished Authorize API

--- src/JoyOI.UserCenter/Controllers/ApiController.cs
+++ src/JoyOI.UserCenter/Controllers/ApiController.cs
@@ -15,6 +15,7 @@
     {
         private static Random _random = new Random();
         private static string _randomStringDictionary = "qwertyuiopasdfghjklzxcvbnm1234567890";
+
         private static string _generateString(int length)
         {
             var sb = new StringBuilder();
@@ -24,6 +25,25 @@
             }
             return sb.ToString();
         }
+
+        private static string _generateCallBackUrl(string url, string code)
+        {
+            string baseUrl;
+            QueryString queryString;
+            var queryStringStartPosition = url.LastIndexOf('?');
+
+            if (queryStringStartPosition >= 0)
+            {
+                baseUrl = url.Substring(0, queryStringStartPosition);
+                queryString = new QueryString(url.Substring(queryStringStartPosition));
+                queryString = queryString.Add("code", code);
+                return baseUrl + queryString.ToString();
+            }
+            else
+            {
+                return url + "?code=" + code;
+            }
+        }

         public Application Application
         {
@@ -123,8 +143,7 @@
                     }
                 }
                 DB.SaveChanges();
-                // TODO: generate redirect url
-                return Redirect(CallBackUrl);
+                return Redirect(_generateCallBackUrl(CallBackUrl, openId.Code));
             }
         }
     }


SplitView cheat, make a list with all logical content and return the enumerator

--- src/MahApps.Metro/Controls/SplitView/SplitView.cs
+++ src/MahApps.Metro/Controls/SplitView/SplitView.cs
@@ -1,6 +1,7 @@
 namespace MahApps.Metro.Controls
 {
     using System;
+    using System.Collections;
     using System.Windows;
     using System.Windows.Controls;
     using System.Windows.Input;
@@ -154,7 +155,7 @@
         /// </summary>
         /// <returns>The identifier for the <see cref="Pane" /> dependency property.</returns>
         public static readonly DependencyProperty PaneProperty =
-            DependencyProperty.Register("Pane", typeof(UIElement), typeof(SplitView), new PropertyMetadata(null));
+            DependencyProperty.Register("Pane", typeof(UIElement), typeof(SplitView), new PropertyMetadata(null, UpdateLogicalChild));

         /// <summary>
         ///     Gets or sets the contents of the pane of a <see cref="SplitView" />.
@@ -277,6 +278,46 @@
             if (this.lightDismissLayer != null)
             {
                 this.lightDismissLayer.MouseDown += this.OnLightDismiss;
+            }
+        }
+
+        private static void UpdateLogicalChild(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
+        {
+            if (!(dependencyObject is SplitView splitView))
+            {
+                return;
+            }
+
+            if (e.OldValue is FrameworkElement oldChild)
+            {
+                splitView.RemoveLogicalChild(oldChild);
+            }
+
+            if (e.NewValue is FrameworkElement newChild)
+            {
+                splitView.AddLogicalChild(newChild);
+                newChild.DataContext = splitView.DataContext;
+            }
+        }
+
+        /// <inheritdoc />
+        protected override IEnumerator LogicalChildren
+        {
+            get
+            {
+                // cheat, make a list with all logical content and return the enumerator
+                ArrayList children = new ArrayList();
+                if (this.Pane != null)
+                {
+                    children.Add(this.Pane);
+                }
+
+                if (this.Content != null)
+                {
+                    children.Add(this.Content);
+                }
+
+                return children.GetEnumerator();
             }
         }


Fix verbose if-def

Fix verbose if-def

--- src/MsgPack/Serialization/SerializationContext.cs
+++ src/MsgPack/Serialization/SerializationContext.cs
@@ -954,7 +954,7 @@
 			}
 		}

-#if !UNITY && !UNITY
+#if !UNITY
 		[Preserve( AllMembers = true )]
 		private static class SerializerGetter<T>
 		{


Add test for NH-2566

Add test for NH-2566

--- src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs
+++ src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs
@@ -246,6 +246,14 @@
 			Assert.That(query.Count, Is.EqualTo(830));
 		}

+		[Test]
+		public void GroupByAndTake()
+		{
+			//NH-2566
+			var names = db.Users.GroupBy(p => p.Name).Select(g => g.Key).Take(3).ToList();
+			Assert.That(names.Count, Is.EqualTo(3));
+		}
+
 		private static void CheckGrouping<TKey, TElement>(IEnumerable<IGrouping<TKey, TElement>> groupedItems, Func<TElement, TKey> groupBy)
 		{
 			var used = new HashSet<object>();


Regenerate async code for #2794

Regenerate async code for #2794

--- src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs
+++ src/NHibernate/Async/Tool/hbm2ddl/SchemaExport.cs
@@ -525,7 +525,11 @@
 			{
 				if (connectionProvider != null)
 				{
-					connectionProvider.CloseConnection(connection);
+					if (connection != null)
+					{
+						connectionProvider.CloseConnection(connection);
+					}
+
 					connectionProvider.Dispose();
 				}
 			}


Refactoring, use logic already in place.

--- src/Projects/MyCouch/Querying/ViewQueryOptions.cs
+++ src/Projects/MyCouch/Querying/ViewQueryOptions.cs
@@ -101,7 +101,7 @@
         /// <returns></returns>
         public virtual string GetKeysAsJson()
         {
-            if (Keys == null || !Keys.Any())
+            if (!HasKeys)
                 return "{}";

             return string.Format("{{\"keys\":[{0}]}}",


Visitor shouldn't know about it's children.

This was an artifact from extracting the base class.

--- src/gitnstats/Visitor.cs
+++ src/gitnstats/Visitor.cs
@@ -4,12 +4,12 @@
 {
     public abstract class Visitor
     {
-        public delegate void VisitedHandler(CommitVisitor visitor, Commit commit);
+        public delegate void VisitedHandler(Visitor visitor, Commit commit);
         public event VisitedHandler Visited;

         public abstract void Walk(Commit commit);

-        protected virtual void OnVisited(CommitVisitor visitor, Commit commit)
+        protected virtual void OnVisited(Visitor visitor, Commit commit)
         {
             Visited?.Invoke(visitor, commit);
         }


Pequena alteração de exemplo.

Pequena alteração de exemplo.

--- src/SeLigaNoBlog/SeLigaNoBlog.Test/ArtigoTests.cs
+++ src/SeLigaNoBlog/SeLigaNoBlog.Test/ArtigoTests.cs
@@ -17,7 +17,7 @@
             var artigo = new Artigo(titulo, url);

             // Assert
-            Assert.AreEqual(titulo, artigo.Titulo);
+            Assert.AreEqual(titulo, artigo.Titulo);
             Assert.AreEqual(url, artigo.Url);
         }


fix merge of AutoWireContainer some more.

--- src/ServiceStack/ServiceHost/AutoWireContainer.cs
+++ src/ServiceStack/ServiceHost/AutoWireContainer.cs
@@ -168,9 +168,9 @@
         {
             var serviceFactory = GenerateAutoWireFn<T>();
             container.Register(serviceFactory).ReusedWithin(this.Scope);
-        }

 			this.resolveFnMap[typeof(T)] = () => serviceFactory(this.container);
+		}

         /// <summary>
         /// Registers the type in the IoC container passed in the constructor and
@@ -188,9 +188,9 @@
             Func<Container, TAs> fn = c => serviceFactory(c);

             this.container.Register(fn).ReusedWithin(this.Scope);
-        }

 			this.resolveFnMap[typeof(TAs)] = () => fn(this.container);
+        }

         /// <summary>
         /// Registers the type in the IoC container and


RenderAction test needs to use stub RenderActionController

Learning once again why you run all unit tests before pushing a
set of commits.

--- src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs
+++ src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs
@@ -638,7 +638,7 @@
         {
             public IController CreateController(RequestContext requestContext, string controllerName)
             {
-                return new StubController();
+                return new RenderActionController();
             }

             public void ReleaseController(IController controller)


Fix bug (#2227)

Fix bug (#2227)

--- src/Stratis.Bitcoin/Consensus/ConsensusManager.cs
+++ src/Stratis.Bitcoin/Consensus/ConsensusManager.cs
@@ -466,9 +466,10 @@

             // Partial validation should continue if:
             //  1: There are more blocks to validate.
-            //  2: A full validation was done and succeeded.
-            bool fullValidationSucceeded = (connectBlocksResult != null) && connectBlocksResult.Succeeded;
-            if ((chainedHeaderBlocksToValidate != null) && fullValidationSucceeded)
+            //  2: A full validation did not fail (if full validation was needed).
+            bool nextValidationNeeded = chainedHeaderBlocksToValidate != null;
+            bool fullValidationFailed = (connectBlocksResult != null) && !connectBlocksResult.Succeeded;
+            if (nextValidationNeeded && !fullValidationFailed)
             {
                 this.logger.LogTrace("Partial validation of {0} block will be started.", chainedHeaderBlocksToValidate.Count);


Improve test case a little bit.

Improve test case a little bit.

--- osu.Desktop.Tests/Visual/TestCaseManiaPlayfield.cs
+++ osu.Desktop.Tests/Visual/TestCaseManiaPlayfield.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE

+using System;
 using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Extensions.IEnumerableExtensions;
@@ -32,6 +33,8 @@

         public TestCaseManiaPlayfield()
         {
+            var rng = new Random(1337);
+
             AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
             AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
             AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
@@ -51,10 +54,12 @@
             {
                 var playfield = createPlayfield(4, SpecialColumnPosition.Normal);

-                var note = new DrawableNote(new Note(), ManiaAction.Key1)
+                int col = rng.Next(0, 4);
+
+                var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1)
                 {
                     Judgement = new ManiaJudgement { Result = HitResult.Hit },
-                    AccentColour = Color4.Green
+                    AccentColour = playfield.Columns.ElementAt(col).AccentColour
                 };

                 playfield.OnJudgement(note);